多媒体容器格式是音视频开发的基石,MP4作为最流行的容器格式,其标准版本和碎片化版本在流媒体中扮演不同角色。本文将深入解析MP4和Fragmented MP4的结构差异、工作原理,以及在Android中如何利用它们实现高效流媒体播放。


一、多媒体容器格式基础

1. 容器格式的核心作用

// 容器格式的核心功能抽象
interface MediaContainer {
    // 封装功能
    fun encapsulateStreams(
        videoStream: VideoStream,
        audioStream: AudioStream,
        subtitleStream: SubtitleStream? = null
    ): ByteArray
    
    // 解封装功能
    fun demultiplex(data: ByteArray): List<MediaStream>
    
    // 元数据管理
    fun getMetadata(): MediaMetadata
    
    // 随机访问支持
    fun seekToTimestamp(timestamp: Long): SeekResult
    
    // 流式传输支持
    fun supportsStreaming(): Boolean
}

// 容器格式关键概念
data class MediaStream(
    val type: StreamType,
    val codec: CodecInfo,
    val timescale: Long,          // 时间基准
    val duration: Long,           // 时长(按时间基准)
    val bitrate: Int,             // 比特率
    val extradata: ByteArray? = null // 编解码器特定数据
)

enum class StreamType {
    VIDEO, AUDIO, SUBTITLE, DATA
}

2. 常见容器格式对比

格式文件扩展名编码格式支持流媒体支持应用场景
MP4.mp4, .m4vH.264/5, AAC, MP3有限(需moov前置)本地存储,点播
Fragmented MP4.mp4, .m4sH.264/5, AAC, Opus优秀(DASH, HLS) 流媒体,直播
WebM.webmVP8/9, AV1, Opus, Vorbis良好(WebRTC)Web视频,开源项目
MKV.mkv几乎所有格式良好高清存储,多字幕
FLV.flvH.264, AAC, MP3优秀(传统直播) 传统直播流
TS.tsH.264, AAC优秀(传统HLS)广播电视,传统HLS

3. ISO BMFF标准家族

ISO Base Media File Format (ISO/IEC 14496-12)
├── MP4 (MPEG-4 Part 14)
├── 3GP (3GPP)
├── MOV (QuickTime)
├── Fragmented MP4
└── CMAF (Common Media Application Format)

二、MP4容器格式深度解析

1. MP4文件结构详解

// MP4文件结构解析器
class MP4Parser {
    
    // MP4盒子(Box)结构
    data class Box(
        val size: Long,           // 盒子大小(包含header)
        val type: String,         // 4字符类型标识
        val largeSize: Long? = null, // 当size=1时使用
        val userType: ByteArray? = null, // 'uuid'类型时使用
        val data: ByteArray,      // 盒子数据
        val children: List<Box> = emptyList() // 子盒子
    ) {
        companion object {
            // 标准盒子类型
            const val TYPE_FTYP = "ftyp"
            const val TYPE_MOOV = "moov"
            const val TYPE_MDAT = "mdat"
            const val TYPE_MVHD = "mvhd" // Movie Header
            const val TYPE_TRAK = "trak" // Track
            const val TYPE_MDIA = "mdia" // Media
            const val TYPE_MINF = "minf" // Media Information
            const val TYPE_STBL = "stbl" // Sample Table
            const val TYPE_STSD = "stsd" // Sample Description
            const val TYPE_STTS = "stts" // Time-to-Sample
            const val TYPE_STSC = "stsc" // Sample-to-Chunk
            const val TYPE_STSZ = "stsz" // Sample Size
            const val TYPE_STCO = "stco" // Chunk Offset (32-bit)
            const val TYPE_CO64 = "co64" // Chunk Offset (64-bit)
            const val TYPE_CTTS = "ctts" // Composition Time Offset
        }
    }
    
    // 解析MP4文件
    fun parseMP4(file: File): MP4File {
        val boxes = mutableListOf<Box>()
        val input = FileInputStream(file).channel
        
        var position = 0L
        while (position < input.size()) {
            val box = parseBox(input, position)
            boxes.add(box)
            position += box.size
        }
        
        return MP4File(boxes)
    }
    
    private fun parseBox(channel: FileChannel, position: Long): Box {
        // 读取盒子header
        val buffer = ByteBuffer.allocate(8)
        channel.read(buffer, position)
        buffer.flip()
        
        val size = buffer.int.toLong() and 0xFFFFFFFFL
        val type = String(ByteArray(4).apply {
            buffer.get(this, 0, 4)
        })
        
        // 处理扩展大小
        val largeSize = if (size == 1L) {
            val largeBuffer = ByteBuffer.allocate(8)
            channel.read(largeBuffer, position + 8)
            largeBuffer.flip()
            largeBuffer.long
        } else null
        
        // 处理uuid类型
        val userType = if (type == "uuid") {
            val uuidBuffer = ByteBuffer.allocate(16)
            channel.read(uuidBuffer, position + 8)
            uuidBuffer.array()
        } else null
        
        // 计算数据位置和大小
        val headerSize = when {
            largeSize != null -> 16
            userType != null -> 24
            else -> 8
        }
        
        val dataSize = (largeSize ?: size) - headerSize
        val dataBuffer = ByteBuffer.allocate(dataSize.toInt())
        channel.read(dataBuffer, position + headerSize)
        
        // 解析子盒子(如果是容器盒子)
        val children = if (isContainerBox(type)) {
            parseChildBoxes(dataBuffer.array())
        } else emptyList()
        
        return Box(
            size = largeSize ?: size,
            type = type,
            largeSize = largeSize,
            userType = userType,
            data = dataBuffer.array(),
            children = children
        )
    }
    
    private fun isContainerBox(type: String): Boolean {
        return when (type) {
            "moov", "trak", "mdia", "minf", "stbl", "edts" -> true
            else -> false
        }
    }
}

// MP4文件结构表示
data class MP4File(
    val boxes: List<MP4Parser.Box>,
    val ftyp: FtypBox? = null,
    val moov: MoovBox? = null,
    val mdat: MdatBox? = null
) {
    init {
        // 提取关键盒子
        boxes.forEach { box ->
            when (box.type) {
                MP4Parser.Box.TYPE_FTYP -> ftyp = parseFtyp(box)
                MP4Parser.Box.TYPE_MOOV -> moov = parseMoov(box)
                MP4Parser.Box.TYPE_MDAT -> mdat = parseMdat(box)
            }
        }
    }
    
    // 获取媒体信息
    fun getMediaInfo(): MediaInfo {
        return moov?.let { moov ->
            MediaInfo(
                duration = moov.mvhd.duration / moov.mvhd.timescale,
                tracks = moov.traks.map { track ->
                    TrackInfo(
                        type = track.mdia.hdlr.handlerType,
                        codec = track.mdia.minf.stbl.stsd.codecType,
                        duration = track.mdia.mdhd.duration / track.mdia.mdhd.timescale,
                        bitrate = calculateBitrate(track)
                    )
                }
            )
        } ?: throw IllegalStateException("moov box not found")
    }
}

2. MP4关键盒子详解

ftyp(File Type)盒子

data class FtypBox(
    val majorBrand: String,      // 主要品牌(如:isom, mp41, avc1)
    val minorVersion: Int,       // 次要版本
    val compatibleBrands: List<String> // 兼容品牌
) {
    companion object {
        // 常见品牌
        const val BRAND_ISOM = "isom"  // ISO Base Media
        const val BRAND_MP41 = "mp41"  // MP4 v1
        const val BRAND_MP42 = "mp42"  // MP4 v2
        const val BRAND_AVC1 = "avc1"  // AVC (H.264)
        const val BRAND_HEV1 = "hev1"  // HEVC (H.265)
        const val BRAND_DASH = "dash"  // DASH流媒体
        const val BRAND_MSDH = "msdh"  // Microsoft Smooth Streaming
    }
}

moov(Movie)盒子结构

data class MoovBox(
    val mvhd: MvhdBox,           // Movie Header
    val traks: List<TrakBox>     // Tracks
)

data class MvhdBox(
    val version: Int,            // 版本(0或1)
    val creationTime: Long,      // 创建时间
    val modificationTime: Long,  // 修改时间
    val timescale: Long,         // 时间基准(每秒刻度数)
    val duration: Long,          // 时长(按时间基准)
    val rate: Int,               // 播放速率(16.16定点数)
    val volume: Short,           // 音量(8.8定点数)
    val matrix: IntArray,        // 变换矩阵(9个32位定点数)
    val nextTrackID: Int         // 下一个轨道ID
)

data class TrakBox(
    val tkhd: TkhdBox,           // Track Header
    val mdia: MdiaBox            // Media
)

data class TkhdBox(
    val version: Int,
    val flags: Int,              // 轨道标志
    val creationTime: Long,
    val modificationTime: Long,
    val trackID: Int,            // 轨道ID
    val duration: Long,
    val layer: Short,            // 图层顺序
    val alternateGroup: Short,   // 轨道组
    val volume: Short,
    val width: Int,              // 宽度(16.16定点数)
    val height: Int,             // 高度(16.16定点数)
    val matrix: IntArray
)

data class MdiaBox(
    val mdhd: MdhdBox,           // Media Header
    val hdlr: HdlrBox,           // Handler Reference
    val minf: MinfBox            // Media Information
)

data class MdhdBox(
    val version: Int,
    val creationTime: Long,
    val modificationTime: Long,
    val timescale: Long,         // 媒体时间基准
    val duration: Long,
    val language: Short          // ISO-639-2/T语言代码
)

stbl(Sample Table)关键结构

data class StblBox(
    val stsd: StsdBox,           // Sample Description
    val stts: SttsBox,           // Decoding Time to Sample
    val stsc: StscBox,           // Sample To Chunk
    val stsz: StszBox,           // Sample Size
    val stco: StcoBox,           // Chunk Offset (32-bit)
    val co64: Co64Box? = null,   // Chunk Offset (64-bit)
    val ctts: CttsBox? = null    // Composition Time Offset
)

// STTS:解码时间戳映射
data class SttsBox(
    val entries: List<SttsEntry> // 时间戳映射表
) {
    data class SttsEntry(
        val sampleCount: Int,    // 连续样本数
        val sampleDelta: Int     // 样本时间间隔
    )
    
    // 根据样本索引获取解码时间
    fun getDecodeTime(sampleIndex: Int): Long {
        var remaining = sampleIndex
        var time = 0L
        
        for (entry in entries) {
            if (remaining < entry.sampleCount) {
                return time + remaining * entry.sampleDelta
            }
            time += entry.sampleCount * entry.sampleDelta
            remaining -= entry.sampleCount
        }
        
        throw IndexOutOfBoundsException("Sample index out of range")
    }
}

// STSC:样本到块的映射
data class StscBox(
    val entries: List<StscEntry>
) {
    data class StscEntry(
        val firstChunk: Int,     // 第一个块号
        val samplesPerChunk: Int, // 每块样本数
        val sampleDescriptionID: Int // 样本描述ID
    )
}

// STCO/CO64:块偏移
data class StcoBox(
    val chunkOffsets: List<Long> // 块在文件中的偏移
)

data class Co64Box(
    val chunkOffsets: List<Long> // 64位块偏移
)

// CTTS:合成时间偏移(B帧相关)
data class CttsBox(
    val entries: List<CttsEntry>
) {
    data class CttsEntry(
        val sampleCount: Int,    // 连续样本数
        val sampleOffset: Int    // 合成时间偏移
    )
    
    // 计算PTS(Presentation Time Stamp)
    fun getPresentationTime(sampleIndex: Int, decodeTime: Long): Long {
        var remaining = sampleIndex
        var offset = 0L
        
        for (entry in entries) {
            if (remaining < entry.sampleCount) {
                offset = entry.sampleOffset.toLong()
                break
            }
            remaining -= entry.sampleCount
        }
        
        return decodeTime + offset
    }
}

3. MP4的局限性分析

class MP4LimitationsAnalyzer {
    
    // moov前置问题
    class MoovFirstProblem {
        companion object {
            // moov位置检测
            fun findMoovPosition(file: File): MoovPosition {
                val parser = MP4Parser()
                val mp4File = parser.parseMP4(file)
                
                var moovOffset = 0L
                var currentOffset = 0L
                
                for (box in mp4File.boxes) {
                    when (box.type) {
                        "moov" -> return MoovPosition(currentOffset, MoovLocation.MIDDLE)
                        "ftyp" -> currentOffset += box.size
                        "mdat" -> {
                            // 如果先遇到mdat,moov在末尾
                            if (moovOffset == 0L) {
                                return MoovPosition(-1, MoovLocation.END)
                            }
                        }
                    }
                }
                
                return MoovPosition(moovOffset, MoovLocation.START)
            }
            
            // 修复moov前置(优化网络播放)
            fun moveMoovToFront(input: File, output: File) {
                val parser = MP4Parser()
                val mp4File = parser.parseMP4(input)
                
                // 重新排列盒子顺序:ftyp -> moov -> mdat
                val outputStream = FileOutputStream(output)
                val writer = MP4BoxWriter(outputStream.channel())
                
                // 写入ftyp
                mp4File.ftyp?.let { writer.writeBox(it) }
                
                // 写入moov
                mp4File.moov?.let { writer.writeBox(it) }
                
                // 写入mdat
                mp4File.mdat?.let { writer.writeBox(it) }
                
                writer.close()
            }
        }
        
        data class MoovPosition(
            val offset: Long,
            val location: MoovLocation
        )
        
        enum class MoovLocation {
            START, MIDDLE, END
        }
    }
    
    // 流式传输问题
    class StreamingLimitations {
        // MP4不适合流式传输的原因
        val limitations = listOf(
            Limitation(
                name = "索引集中",
                description = "stbl中的所有索引都集中在文件开头或结尾",
                impact = Impact.HIGH,
                solution = "使用Fragmented MP4将索引分散"
            ),
            Limitation(
                name = "不支持动态更新",
                description = "无法在播放过程中添加新内容",
                impact = Impact.HIGH,
                solution = "使用Fragmented MP4或流媒体协议"
            ),
            Limitation(
                name = "随机访问成本高",
                description = "跳转时需要加载整个stbl",
                impact = Impact.MEDIUM,
                solution = "使用Fragmented MP4的moof索引"
            )
        )
        
        data class Limitation(
            val name: String,
            val description: String,
            val impact: Impact,
            val solution: String
        )
        
        enum class Impact {
            LOW, MEDIUM, HIGH, CRITICAL
        }
    }
}

三、Fragmented MP4(fMP4)深度解析

1. fMP4设计哲学

// fMP4设计原理
class FragmentedMP4Design {
    
    // 关键设计原则
    data class DesignPrinciple(
        val principle: String,
        val description: String,
        val benefit: String
    )
    
    companion object {
        val PRINCIPLES = listOf(
            DesignPrinciple(
                principle = "分片独立",
                description = "每个媒体片段(fragment)包含完整的解码和渲染信息",
                benefit = "支持流式传输和随机访问"
            ),
            DesignPrinciple(
                principle = "索引分散",
                description = "媒体样本索引分散在各个moof盒子中",
                benefit = "无需等待完整文件即可播放"
            ),
            DesignPrinciple(
                principle = "动态更新",
                description = "支持在播放过程中追加新的媒体片段",
                benefit = "适合直播和动态内容"
            ),
            DesignPrinciple(
                principle = "轨道片段化",
                description = "每个轨道可以独立分片,支持选择性下载",
                benefit = "节省带宽,支持自适应流"
            )
        )
    }
    
    // fMP4与MP4结构对比
    class StructureComparison {
        data class Comparison(
            val component: String,
            val mp4Structure: String,
            val fmp4Structure: String,
            val difference: String
        )
        
        val comparisons = listOf(
            Comparison(
                component = "索引结构",
                mp4Structure = "集中式stbl",
                fmp4Structure = "分布式trun",
                difference = "fMP4在每个moof中都有局部索引"
            ),
            Comparison(
                component = "媒体数据",
                mp4Structure = "单个mdat",
                fmp4Structure = "多个mdat(每个片段一个)",
                difference = "fMP4数据分段存储"
            ),
            Comparison(
                component = "随机访问",
                mp4Structure = "依赖完整stbl",
                fmp4Structure = "依赖sidx和moof",
                difference = "fMP4支持基于片段的随机访问"
            ),
            Comparison(
                component = "动态更新",
                mp4Structure = "不支持",
                fmp4Structure = "支持追加片段",
                difference = "fMP4适合直播和动态内容"
            )
        )
    }
}

2. fMP4文件结构

// fMP4文件结构解析
class FragmentedMP4Parser : MP4Parser() {
    
    // fMP4特定盒子类型
    companion object {
        const val TYPE_MOOF = "moof" // Movie Fragment
        const val TYPE_MFRA = "mfra" // Movie Fragment Random Access
        const val TYPE_TRAF = "traf" // Track Fragment
        const val TYPE_TFHD = "tfhd" // Track Fragment Header
        const val TYPE_TRUN = "trun" // Track Fragment Run
        const val TYPE_TFDT = "tfdt" // Track Fragment Decode Time
        const val TYPE_SIDX = "sidx" // Segment Index
        const val TYPE_SSIX = "ssix" // Subsegment Index
    }
    
    // 解析fMP4文件
    fun parseFragmentedMP4(file: File): FragmentedMP4File {
        val boxes = parseMP4(file).boxes
        val fragments = mutableListOf<MovieFragment>()
        
        var currentFragment: MovieFragment? = null
        boxes.forEach { box ->
            when (box.type) {
                TYPE_MOOF -> {
                    currentFragment?.let { fragments.add(it) }
                    currentFragment = MovieFragment().apply {
                        moof = parseMoof(box)
                    }
                }
                TYPE_MDAT -> {
                    currentFragment?.mdat = parseMdat(box)
                }
                TYPE_SIDX -> {
                    // 处理分片索引
                }
            }
        }
        
        currentFragment?.let { fragments.add(it) }
        
        return FragmentedMP4File(
            ftyp = boxes.find { it.type == TYPE_FTYP }?.let { parseFtyp(it) },
            moov = boxes.find { it.type == TYPE_MOOV }?.let { parseMoov(it) },
            fragments = fragments,
            sidx = boxes.find { it.type == TYPE_SIDX }?.let { parseSidx(it) }
        )
    }
    
    // Movie Fragment结构
    data class MovieFragment(
        var moof: MoofBox? = null,
        var mdat: MdatBox? = null
    )
    
    // moof(Movie Fragment)盒子
    data class MoofBox(
        val mfhd: MfhdBox,           // Movie Fragment Header
        val trafs: List<TrafBox>     // Track Fragments
    )
    
    data class MfhdBox(
        val sequenceNumber: Int      // 片段序列号
    )
    
    // traf(Track Fragment)盒子
    data class TrafBox(
        val tfhd: TfhdBox,           // Track Fragment Header
        val tfdt: TfdtBox? = null,   // Track Fragment Decode Time
        val trun: TrunBox? = null,   // Track Fragment Run
        val sdtp: SdtpBox? = null    // Independent and Disposable Samples
    )
    
    data class TfhdBox(
        val trackID: Int,            // 引用的轨道ID
        val baseDataOffset: Long? = null,
        val sampleDescriptionIndex: Int? = null,
        val defaultSampleDuration: Int? = null,
        val defaultSampleSize: Int? = null,
        val defaultSampleFlags: Int? = null
    )
    
    data class TfdtBox(
        val version: Int,            // 0或1
        val baseMediaDecodeTime: Long // 基础媒体解码时间
    )
    
    // trun(Track Fragment Run)盒子 - fMP4核心
    data class TrunBox(
        val version: Int,
        val flags: Int,
        val sampleCount: Int,
        val dataOffset: Int? = null,
        val firstSampleFlags: Int? = null,
        val samples: List<TrunSample>
    ) {
        data class TrunSample(
            val sampleDuration: Int? = null,
            val sampleSize: Int? = null,
            val sampleFlags: Int? = null,
            val sampleCompositionTimeOffset: Int? = null
        )
        
        // 计算样本在文件中的位置
        fun calculateSamplePositions(startOffset: Long): List<Long> {
            val positions = mutableListOf<Long>()
            var currentOffset = startOffset + (dataOffset ?: 0)
            
            samples.forEach { sample ->
                positions.add(currentOffset)
                currentOffset += (sample.sampleSize ?: 0).toLong()
            }
            
            return positions
        }
    }
    
    // sidx(Segment Index)盒子
    data class SidxBox(
        val version: Int,
        val referenceID: Int,
        val timescale: Int,
        val earliestPresentationTime: Long,
        val firstOffset: Long,
        val references: List<SidxReference>
    ) {
        data class SidxReference(
            val referenceType: Int,      // 0=媒体数据,1=索引数据
            val referencedSize: Long,
            val subsegmentDuration: Long,
            val startsWithSAP: Boolean,  // 是否以SAP(流访问点)开始
            val sapType: Int,
            val sapDeltaTime: Int
        )
        
        // 根据时间查找分片
        fun findSegmentForTime(timeMs: Long): Int? {
            var accumulatedTime = 0L
            
            references.forEachIndexed { index, ref ->
                val segmentDurationMs = (ref.subsegmentDuration * 1000) / timescale
                accumulatedTime += segmentDurationMs
                
                if (timeMs < accumulatedTime) {
                    return index
                }
            }
            
            return null
        }
    }
}

3. fMP4在流媒体协议中的应用

// fMP4在DASH中的应用
class DASHWithFMP4 {
    
    // MPD(Media Presentation Description)结构
    data class MPD(
        val type: MPDType,              // 静态或动态
        val availabilityStartTime: String? = null,
        val availabilityEndTime: String? = null,
        val mediaPresentationDuration: String? = null,
        val minBufferTime: String,
        val periods: List<Period>
    ) {
        enum class MPDType {
            STATIC, DYNAMIC
        }
    }
    
    data class Period(
        val start: String? = null,
        val duration: String? = null,
        val adaptationSets: List<AdaptationSet>
    )
    
    data class AdaptationSet(
        val mimeType: String,           // 如:video/mp4
        val codecs: String,             // 如:avc1.640028
        val segmentAlignment: Boolean = true,
        val bitstreamSwitching: Boolean = false,
        val representations: List<Representation>
    )
    
    data class Representation(
        val id: String,
        val bandwidth: Int,             // 比特率(bps)
        val width: Int? = null,
        val height: Int? = null,
        val frameRate: String? = null,
        val segmentTemplate: SegmentTemplate? = null,
        val baseURL: String? = null
    )
    
    data class SegmentTemplate(
        val media: String,              // 媒体URL模板
        val initialization: String? = null, // 初始化段
        val duration: Int,              // 分片时长(时间基准单位)
        val startNumber: Int,           // 起始编号
        val timescale: Int              // 时间基准
    )
    
    // DASH分片命名模式
    class SegmentNamingPattern {
        companion object {
            // URL模板示例
            // $RepresentationID$ - 表示ID
            // $Number$ - 分片编号
            // $Time$ - 分片起始时间
            // $Bandwidth$ - 比特率
            
            fun generateSegmentURL(
                template: String,
                representationID: String,
                number: Int,
                time: Long,
                bandwidth: Int
            ): String {
                return template
                    .replace("\$RepresentationID\$", representationID)
                    .replace("\$Number\$", number.toString())
                    .replace("\$Time\$", time.toString())
                    .replace("\$Bandwidth\$", bandwidth.toString())
            }
            
            // 初始化段:包含moov
            val INIT_SEGMENT_PATTERN = "\$RepresentationID\$-init.mp4"
            
            // 媒体段:包含moof+mdat
            val MEDIA_SEGMENT_PATTERN = "\$RepresentationID\$-\$Number\$.m4s"
        }
    }
}

// fMP4在HLS中的应用
class HLSWithFMP4 {
    
    // HLS播放列表结构
    data class HLSPlaylist(
        val version: Int = 6,           // HLS版本,6+支持fMP4
        val targetDuration: Int,        // 目标分片时长(秒)
        val mediaSequence: Long,        // 媒体序列号
        val playlistType: PlaylistType? = null,
        val segments: List<HLSSegment>,
        val renditions: List<Rendition> = emptyList()
    ) {
        enum class PlaylistType {
            VOD, EVENT, LIVE
        }
    }
    
    data class HLSSegment(
        val duration: Double,           // 分片时长(秒)
        val uri: String,                // 分片URI
        val byteRange: ByteRange? = null, // 字节范围(对于fMP4打包在单个文件)
        val discontinuity: Boolean = false, // 不连续标志
        val programDateTime: String? = null // 节目日期时间
    ) {
        data class ByteRange(
            val offset: Long,           // 起始偏移
            val length: Long            // 长度
        )
    }
    
    data class Rendition(
        val type: RenditionType,
        val groupID: String,
        val name: String,
        val uri: String,
        val default: Boolean = false,
        val autoselect: Boolean = false,
        val forced: Boolean = false
    ) {
        enum class RenditionType {
            AUDIO, VIDEO, SUBTITLES, CLOSED_CAPTIONS
        }
    }
    
    // HLS fMP4分片生成
    class FMP4Segmenter {
        fun createInitSegment(moovData: ByteArray): ByteArray {
            // 初始化段 = ftyp + moov
            return concatenateBoxes(
                createFtypBox("mp42", listOf("mp42", "iso6", "dash")),
                moovData
            )
        }
        
        fun createMediaSegment(
            moofData: ByteArray,
            mdatData: ByteArray
        ): ByteArray {
            // 媒体段 = moof + mdat
            return concatenateBoxes(moofData, mdatData)
        }
        
        // 创建HLS播放列表条目
        fun createHLSEntry(
            segmentData: ByteArray,
            segmentIndex: Long,
            duration: Double,
            isIndependent: Boolean = true
        ): HLSSegment {
            return HLSSegment(
                duration = duration,
                uri = "segment_$segmentIndex.m4s",
                byteRange = if (isIndependent) null else {
                    // 如果多个分片打包在一个文件
                    calculateByteRange(segmentIndex, segmentData.size)
                },
                discontinuity = segmentIndex == 0L // 第一个分片标记不连续
            )
        }
    }
}

4. CMAF(Common Media Application Format)

// CMAF规范实现
class CMAFImplementation {
    
    // CMAF Header(CMAF头)
    data class CMAFHeader(
        val tracks: List<CMAFTrack>,
        val fileType: CMAFFileType = CMAFFileType.CMAF
    ) {
        enum class CMAFFileType {
            CMAF,         // 标准CMAF
            CMAF_TRACK,   // CMAF轨道
            CMAF_SWITCHING // CMAF切换
        }
    }
    
    data class CMAFTrack(
        val trackID: Int,
        val type: TrackType,
        val codec: String,              // RFC 6381编码字符串
        val timescale: Long,
        val duration: Long,
        val trackWidth: Int? = null,
        val trackHeight: Int? = null,
        val sampleEntry: CMAFSampleEntry
    ) {
        enum class TrackType {
            VIDEO, AUDIO, SUBTITLE, TIMED_METADATA
        }
    }
    
    // CMAF Chunk(CMAF块)
    data class CMAFChunk(
        val header: CMAFChunkHeader,
        val samples: List<CMAFSample>
    ) {
        data class CMAFChunkHeader(
            val chunkIndex: Int,
            val trackID: Int,
            val baseDecodeTime: Long,
            val sampleCount: Int,
            val chunkDuration: Long
        )
        
        data class CMAFSample(
            val sampleSize: Int,
            val sampleDuration: Int,
            val sampleFlags: CMAFSampleFlags,
            val compositionTimeOffset: Int? = null,
            val data: ByteArray
        ) {
            data class CMAFSampleFlags(
                val isLeading: Int,         // 前导样本
                val dependsOn: Int,         // 依赖关系
                val isDependedOn: Int,      // 被依赖关系
                val hasRedundancy: Int,     // 冗余
                val paddingValue: Int,      // 填充值
                val isNonSyncSample: Boolean // 是否非关键帧
            )
        }
    }
    
    // CMAF Switching Set(切换集)
    class CMAFSwitchingSet {
        // 同一内容的不同表示形式
        val representations = mutableListOf<CMAFRepresentation>()
        
        fun addRepresentation(rep: CMAFRepresentation) {
            representations.add(rep)
        }
        
        // 查找最佳表示形式(基于带宽)
        fun findBestRepresentation(availableBandwidth: Int): CMAFRepresentation? {
            return representations
                .filter { it.bandwidth <= availableBandwidth }
                .maxByOrNull { it.bandwidth }
        }
    }
    
    data class CMAFRepresentation(
        val id: String,
        val bandwidth: Int,
        val width: Int? = null,
        val height: Int? = null,
        val frameRate: String? = null,
        val codecs: String,
        val initializationSegment: ByteArray,
        val mediaSegments: List<ByteArray>
    )
    
    // CMAF生成器
    class CMAFGenerator {
        fun createCMAFTrack(
            mediaData: ByteArray,
            trackInfo: TrackInfo
        ): CMAFTrack {
            return CMAFTrack(
                trackID = trackInfo.trackID,
                type = when (trackInfo.mimeType.substringBefore("/")) {
                    "video" -> TrackType.VIDEO
                    "audio" -> TrackType.AUDIO
                    else -> TrackType.TIMED_METADATA
                },
                codec = parseCodecString(trackInfo.codecSpecificData),
                timescale = trackInfo.timescale,
                duration = trackInfo.duration,
                trackWidth = trackInfo.width,
                trackHeight = trackInfo.height,
                sampleEntry = createSampleEntry(trackInfo)
            )
        }
        
        fun createCMAFChunk(
            samples: List<MediaSample>,
            chunkIndex: Int,
            trackID: Int
        ): CMAFChunk {
            val cmafSamples = samples.map { sample ->
                CMAFChunk.CMAFSample(
                    sampleSize = sample.size,
                    sampleDuration = sample.duration,
                    sampleFlags = CMAFChunk.CMAFSample.CMAFSampleFlags(
                        isLeading = 0,
                        dependsOn = if (sample.isKeyFrame) 2 else 1,
                        isDependedOn = 1,
                        hasRedundancy = 0,
                        paddingValue = 0,
                        isNonSyncSample = !sample.isKeyFrame
                    ),
                    compositionTimeOffset = sample.compositionOffset,
                    data = sample.data
                )
            }
            
            return CMAFChunk(
                header = CMAFChunk.CMAFChunkHeader(
                    chunkIndex = chunkIndex,
                    trackID = trackID,
                    baseDecodeTime = samples.first().decodeTime,
                    sampleCount = samples.size,
                    chunkDuration = samples.sumOf { it.duration }
                ),
                samples = cmafSamples
            )
        }
    }
}

四、Android中的MP4与fMP4实践

1. 使用MediaExtractor解析容器

class MediaContainerAnalyzer(private val context: Context) {
    
    // 分析MP4文件
    fun analyzeMP4File(filePath: String): ContainerAnalysis {
        val extractor = MediaExtractor()
        
        return try {
            extractor.setDataSource(filePath)
            
            val trackCount = extractor.trackCount
            val tracks = mutableListOf<TrackAnalysis>()
            
            for (i in 0 until trackCount) {
                val format = extractor.getTrackFormat(i)
                val trackAnalysis = analyzeTrack(format, i)
                tracks.add(trackAnalysis)
            }
            
            // 获取容器级信息
            val containerInfo = extractContainerInfo(extractor, filePath)
            
            ContainerAnalysis(
                filePath = filePath,
                containerFormat = containerInfo.format,
                tracks = tracks,
                containerInfo = containerInfo,
                isFragmented = isFragmentedMP4(filePath)
            )
        } finally {
            extractor.release()
        }
    }
    
    private fun analyzeTrack(format: MediaFormat, trackIndex: Int): TrackAnalysis {
        val mime = format.getString(MediaFormat.KEY_MIME) ?: "unknown"
        
        return TrackAnalysis(
            trackIndex = trackIndex,
            mimeType = mime,
            durationUs = format.getLong(MediaFormat.KEY_DURATION),
            bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE),
            codec = parseCodecFromMime(mime),
            sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE, 0),
            channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT, 0),
            width = format.getInteger(MediaFormat.KEY_WIDTH, 0),
            height = format.getInteger(MediaFormat.KEY_HEIGHT, 0),
            frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE, 0),
            iFrameInterval = format.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0),
            csd = extractCodecSpecificData(format)
        )
    }
    
    // 检测是否为fMP4
    private fun isFragmentedMP4(filePath: String): Boolean {
        return try {
            val input = FileInputStream(filePath).channel
            val buffer = ByteBuffer.allocate(1024)
            
            // 读取文件前1KB,查找moof盒子
            input.read(buffer, 0)
            buffer.flip()
            
            val data = ByteArray(buffer.remaining())
            buffer.get(data)
            
            // 简单查找moof标记
            String(data).contains("moof")
        } catch (e: Exception) {
            false
        }
    }
    
    // 提取编解码器特定数据
    private fun extractCodecSpecificData(format: MediaFormat): List<ByteArray> {
        val csdList = mutableListOf<ByteArray>()
        
        // 尝试获取CSD-0, CSD-1, CSD-2
        for (i in 0..2) {
            val key = "csd-$i"
            if (format.containsKey(key)) {
                val byteBuffer = format.getByteBuffer(key)
                byteBuffer?.let {
                    val csd = ByteArray(it.remaining())
                    it.get(csd)
                    csdList.add(csd)
                }
            }
        }
        
        return csdList
    }
    
    data class ContainerAnalysis(
        val filePath: String,
        val containerFormat: String,
        val tracks: List<TrackAnalysis>,
        val containerInfo: ContainerInfo,
        val isFragmented: Boolean
    )
    
    data class TrackAnalysis(
        val trackIndex: Int,
        val mimeType: String,
        val durationUs: Long,
        val bitrate: Int,
        val codec: String,
        val sampleRate: Int,
        val channelCount: Int,
        val width: Int,
        val height: Int,
        val frameRate: Int,
        val iFrameInterval: Int,
        val csd: List<ByteArray>
    )
    
    data class ContainerInfo(
        val format: String,
        val durationMs: Long,
        val fileSize: Long,
        val moovPosition: String,
        val hasFragments: Boolean
    )
}

2. 使用MediaMuxer生成MP4

class MP4Muxer(private val outputPath: String) {
    
    private lateinit var muxer: MediaMuxer
    private var videoTrackIndex = -1
    private var audioTrackIndex = -1
    private var isStarted = false
    
    // 初始化Muxer
    fun initialize(
        videoFormat: MediaFormat? = null,
        audioFormat: MediaFormat? = null
    ) {
        muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
        
        videoFormat?.let {
            videoTrackIndex = muxer.addTrack(it)
        }
        
        audioFormat?.let {
            audioTrackIndex = muxer.addTrack(it)
        }
    }
    
    // 开始混合
    fun start() {
        if (!isStarted) {
            muxer.start()
            isStarted = true
        }
    }
    
    // 写入视频样本
    fun writeVideoSample(
        buffer: ByteBuffer,
        bufferInfo: MediaCodec.BufferInfo
    ) {
        if (videoTrackIndex >= 0 && isStarted) {
            muxer.writeSampleData(videoTrackIndex, buffer, bufferInfo)
        }
    }
    
    // 写入音频样本
    fun writeAudioSample(
        buffer: ByteBuffer,
        bufferInfo: MediaCodec.BufferInfo
    ) {
        if (audioTrackIndex >= 0 && isStarted) {
            muxer.writeSampleData(audioTrackIndex, buffer, bufferInfo)
        }
    }
    
    // 停止并释放资源
    fun release() {
        if (isStarted) {
            muxer.stop()
        }
        muxer.release()
    }
    
    // 生成MP4文件示例
    companion object {
        fun createMP4FromFrames(
            outputPath: String,
            videoFrames: List<VideoFrame>,
            audioSamples: List<AudioSample>? = null
        ) {
            val muxer = MP4Muxer(outputPath)
            
            // 创建视频格式
            val videoFormat = MediaFormat.createVideoFormat(
                MediaFormat.MIMETYPE_VIDEO_AVC,
                videoFrames.first().width,
                videoFrames.first().height
            ).apply {
                setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)
                setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000)
                setInteger(MediaFormat.KEY_FRAME_RATE, 30)
                setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
                
                // 设置SPS和PPS
                videoFrames.first().sps?.let { sps ->
                    val spsBuffer = ByteBuffer.wrap(sps)
                    setByteBuffer("csd-0", spsBuffer)
                }
                
                videoFrames.first().pps?.let { pps ->
                    val ppsBuffer = ByteBuffer.wrap(pps)
                    setByteBuffer("csd-1", ppsBuffer)
                }
            }
            
            // 创建音频格式(如果存在音频)
            val audioFormat = audioSamples?.firstOrNull()?.let { firstSample ->
                MediaFormat.createAudioFormat(
                    MediaFormat.MIMETYPE_AUDIO_AAC,
                    firstSample.sampleRate,
                    firstSample.channelCount
                ).apply {
                    setInteger(MediaFormat.KEY_BIT_RATE, 128_000)
                    setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
                    
                    firstSample.config?.let { config ->
                        val configBuffer = ByteBuffer.wrap(config)
                        setByteBuffer("csd-0", configBuffer)
                    }
                }
            }
            
            // 初始化Muxer
            muxer.initialize(videoFormat, audioFormat)
            muxer.start()
            
            // 写入视频帧
            var presentationTimeUs = 0L
            videoFrames.forEach { frame ->
                val bufferInfo = MediaCodec.BufferInfo().apply {
                    offset = 0
                    size = frame.data.size
                    presentationTimeUs = presentationTimeUs
                    flags = if (frame.isKeyFrame) MediaCodec.BUFFER_FLAG_KEY_FRAME else 0
                }
                
                val buffer = ByteBuffer.wrap(frame.data)
                muxer.writeVideoSample(buffer, bufferInfo)
                
                presentationTimeUs += 33_333 // 30fps,每帧约33.3ms
            }
            
            // 写入音频样本(如果存在)
            audioSamples?.let { samples ->
                var audioTimeUs = 0L
                samples.forEach { sample ->
                    val bufferInfo = MediaCodec.BufferInfo().apply {
                        offset = 0
                        size = sample.data.size
                        presentationTimeUs = audioTimeUs
                        flags = 0
                    }
                    
                    val buffer = ByteBuffer.wrap(sample.data)
                    muxer.writeAudioSample(buffer, bufferInfo)
                    
                    audioTimeUs += (sample.sampleCount * 1_000_000L) / sample.sampleRate
                }
            }
            
            muxer.release()
        }
    }
    
    data class VideoFrame(
        val data: ByteArray,
        val width: Int,
        val height: Int,
        val isKeyFrame: Boolean,
        val sps: ByteArray? = null,
        val pps: ByteArray? = null
    )
    
    data class AudioSample(
        val data: ByteArray,
        val sampleRate: Int,
        val channelCount: Int,
        val sampleCount: Int,
        val config: ByteArray? = null
    )
}

3. 使用ExoPlayer播放fMP4流

class FragmentedMP4Player(
    private val context: Context,
    private val playerView: PlayerView
) {
    
    private lateinit var player: ExoPlayer
    private lateinit var mediaSource: MediaSource
    
    // 初始化播放器
    fun initialize(streamUrl: String, isLive: Boolean = false) {
        // 创建播放器
        player = ExoPlayer.Builder(context)
            .setSeekBackIncrementMs(5000)
            .setSeekForwardIncrementMs(15000)
            .build()
        
        playerView.player = player
        
        // 创建媒体源
        mediaSource = buildMediaSource(Uri.parse(streamUrl), isLive)
        
        // 设置播放器监听
        player.addListener(createPlayerListener())
    }
    
    // 构建媒体源
    private fun buildMediaSource(uri: Uri, isLive: Boolean): MediaSource {
        val dataSourceFactory = DefaultHttpDataSource.Factory()
            .setUserAgent("ExoPlayerDemo")
            .setConnectTimeoutMs(8000)
            .setReadTimeoutMs(15000)
            .setAllowCrossProtocolRedirects(true)
        
        return if (isLive) {
            // 直播流 - 使用HLS或DASH
            if (uri.toString().contains(".m3u8")) {
                HlsMediaSource.Factory(dataSourceFactory)
                    .setAllowChunklessPreparation(true)
                    .createMediaSource(MediaItem.fromUri(uri))
            } else {
                // DASH流
                DashMediaSource.Factory(dataSourceFactory)
                    .createMediaSource(MediaItem.fromUri(uri))
            }
        } else {
            // 点播流 - 使用渐进式或fMP4
            ProgressiveMediaSource.Factory(dataSourceFactory)
                .createMediaSource(MediaItem.fromUri(uri))
        }
    }
    
    // 开始播放
    fun play() {
        player.setMediaSource(mediaSource)
        player.prepare()
        player.playWhenReady = true
    }
    
    // 处理fMP4特定功能
    class FMP4Features {
        // 自适应比特率切换
        fun setupAdaptiveBitrate(player: ExoPlayer) {
            player.addAnalyticsListener(object : AnalyticsListener {
                override fun onBandwidthEstimate(
                    eventTime: EventTime,
                    totalLoadTimeMs: Long,
                    totalBytesLoaded: Long,
                    bitrateEstimate: Long
                ) {
                    // 基于带宽估计调整播放策略
                    adjustPlaybackBasedOnBandwidth(bitrateEstimate)
                }
            })
        }
        
        // 分片加载监控
        fun monitorSegmentLoading(player: ExoPlayer) {
            player.addAnalyticsListener(object : AnalyticsListener {
                override fun onLoadCompleted(
                    eventTime: EventTime,
                    loadEventInfo: LoadEventInfo,
                    mediaLoadData: MediaLoadData
                ) {
                    // 分片加载完成
                    mediaLoadData.dataSpec?.uri?.let { uri ->
                        Log.d("SegmentLoad", "Loaded segment: $uri")
                    }
                }
                
                override fun onLoadCanceled(
                    eventTime: EventTime,
                    loadEventInfo: LoadEventInfo,
                    mediaLoadData: MediaLoadData
                ) {
                    // 分片加载取消(可能由于比特率切换)
                }
            })
        }
        
        // 低延迟优化
        fun optimizeForLowLatency(player: ExoPlayer) {
            // 减少缓冲区
            val loadControl = DefaultLoadControl.Builder()
                .setBufferDurationsMs(1000, 5000, 500, 2000)
                .build()
            
            // 设置轨道选择器偏好低延迟
            val trackSelector = DefaultTrackSelector(context).apply {
                parameters = buildUponParameters()
                    .setMaxVideoBitrate(1_500_000)
                    .setMaxVideoSize(1280, 720)
                    .build()
            }
        }
    }
    
    private fun createPlayerListener(): Player.Listener {
        return object : Player.Listener {
            override fun onPlaybackStateChanged(playbackState: Int) {
                when (playbackState) {
                    Player.STATE_READY -> {
                        Log.d("Player", "播放器准备就绪")
                    }
                    Player.STATE_BUFFERING -> {
                        Log.d("Player", "播放器缓冲中")
                    }
                    Player.STATE_ENDED -> {
                        Log.d("Player", "播放结束")
                    }
                }
            }
            
            override fun onPlayerError(error: PlaybackException) {
                Log.e("Player", "播放错误: ${error.message}")
                // 处理错误,如切换到低码率流
                handlePlaybackError(error)
            }
            
            override fun onVideoSizeChanged(videoSize: VideoSize) {
                Log.d("Player", "视频尺寸变化: ${videoSize.width}x${videoSize.height}")
            }
        }
    }
    
    private fun handlePlaybackError(error: PlaybackException) {
        // 根据错误类型采取不同策略
        when (error.errorCode) {
            PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
            PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT -> {
                // 网络错误,尝试重连
                scheduleReconnect()
            }
            PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED -> {
                // 容器格式错误,可能是fMP4分片损坏
                skipToNextSegment()
            }
            PlaybackException.ERROR_CODE_DECODER_INIT_FAILED -> {
                // 解码器初始化失败,尝试软解码
                switchToSoftwareDecoding()
            }
        }
    }
}

4. 生成fMP4分片

class FMP4SegmentGenerator {
    
    // 生成初始化段(包含moov)
    fun generateInitSegment(
        videoConfig: VideoConfig,
        audioConfig: AudioConfig? = null
    ): ByteArray {
        val output = ByteArrayOutputStream()
        val writer = MP4BoxWriter(output)
        
        // 1. 写入ftyp盒子
        writer.writeBox(createFtypBox())
        
        // 2. 写入moov盒子
        val moovBox = createMoovBox(videoConfig, audioConfig)
        writer.writeBox(moovBox)
        
        return output.toByteArray()
    }
    
    // 生成媒体段(包含moof+mdat)
    fun generateMediaSegment(
        segmentIndex: Int,
        videoSamples: List<VideoSample>,
        audioSamples: List<AudioSample>? = null,
        isLastSegment: Boolean = false
    ): ByteArray {
        val output = ByteArrayOutputStream()
        val writer = MP4BoxWriter(output)
        
        // 1. 写入moof盒子
        val moofBox = createMoofBox(segmentIndex, videoSamples, audioSamples)
        writer.writeBox(moofBox)
        
        // 2. 写入mdat盒子
        val mdatData = createMdatData(videoSamples, audioSamples)
        writer.writeBox(createMdatBox(mdatData))
        
        // 3. 如果是最后一个分片,写入mfra盒子(可选)
        if (isLastSegment) {
            val mfraBox = createMfraBox(segmentIndex)
            writer.writeBox(mfraBox)
        }
        
        return output.toByteArray()
    }
    
    // 创建moof盒子
    private fun createMoofBox(
        segmentIndex: Int,
        videoSamples: List<VideoSample>,
        audioSamples: List<AudioSample>?
    ): Box {
        // 创建mfhd(Movie Fragment Header)
        val mfhd = Box(
            type = "mfhd",
            data = createMfhdData(segmentIndex)
        )
        
        // 创建traf(Track Fragment)列表
        val trafs = mutableListOf<Box>()
        
        // 视频轨道fragment
        trafs.add(createTrafBox(
            trackID = 1,
            baseDecodeTime = calculateBaseDecodeTime(videoSamples),
            samples = videoSamples.map { it.toTrunSample() }
        ))
        
        // 音频轨道fragment(如果存在)
        audioSamples?.let { samples ->
            trafs.add(createTrafBox(
                trackID = 2,
                baseDecodeTime = calculateBaseDecodeTime(samples),
                samples = samples.map { it.toTrunSample() }
            ))
        }
        
        return Box(
            type = "moof",
            children = listOf(mfhd) + trafs
        )
    }
    
    // 创建traf盒子
    private fun createTrafBox(
        trackID: Int,
        baseDecodeTime: Long,
        samples: List<TrunSample>
    ): Box {
        // tfhd(Track Fragment Header)
        val tfhd = Box(
            type = "tfhd",
            data = createTfhdData(trackID)
        )
        
        // tfdt(Track Fragment Decode Time)
        val tfdt = Box(
            type = "tfdt",
            data = createTfdtData(baseDecodeTime)
        )
        
        // trun(Track Fragment Run)
        val trun = Box(
            type = "trun",
            data = createTrunData(samples)
        )
        
        return Box(
            type = "traf",
            children = listOf(tfhd, tfdt, trun)
        )
    }
    
    // 创建sidx盒子(分片索引)
    fun createSidxBox(
        references: List<SidxReference>,
        timescale: Int = 1000
    ): Box {
        val data = ByteArrayOutputStream().apply {
            // version(1) + flags(3)
            writeBytes(byteArrayOf(0, 0, 0, 0))
            
            // reference ID
            writeInt(1)
            
            // timescale
            writeInt(timescale)
            
            // earliest presentation time
            writeLong(0)
            
            // first offset
            writeLong(0)
            
            // reserved
            writeShort(0)
            
            // reference count
            writeShort(references.size.toShort())
            
            // references
            references.forEach { ref ->
                // reference type (1 bit) + referenced size (31 bits)
                val firstWord = (if (ref.isContained) 1 else 0 shl 31) or 
                               (ref.size and 0x7FFFFFFF).toInt()
                writeInt(firstWord)
                
                // subsegment duration
                writeInt(ref.duration)
                
                // starts with SAP (1) + SAP type (3) + SAP delta time (28)
                val sapInfo = (1 shl 31) or 
                             (ref.sapType shl 28) or 
                             (ref.sapDeltaTime and 0x0FFFFFFF)
                writeInt(sapInfo)
            }
        }
        
        return Box(type = "sidx", data = data.toByteArray())
    }
    
    data class VideoSample(
        val data: ByteArray,
        val timestamp: Long,
        val duration: Int,
        val isKeyFrame: Boolean,
        val compositionOffset: Int = 0
    ) {
        fun toTrunSample(): TrunSample {
            return TrunSample(
                duration = duration,
                size = data.size,
                flags = if (isKeyFrame) 0x02000000 else 0x01010000,
                compositionOffset = compositionOffset
            )
        }
    }
    
    data class AudioSample(
        val data: ByteArray,
        val timestamp: Long,
        val duration: Int
    ) {
        fun toTrunSample(): TrunSample {
            return TrunSample(
                duration = duration,
                size = data.size,
                flags = 0x02000000,
                compositionOffset = null
            )
        }
    }
    
    data class TrunSample(
        val duration: Int,
        val size: Int,
        val flags: Int,
        val compositionOffset: Int? = null
    )
    
    data class SidxReference(
        val isContained: Boolean,   // 是否包含在同一个文件中
        val size: Int,              // 引用大小
        val duration: Int,          // 子段时长
        val sapType: Int,           // 流访问点类型
        val sapDeltaTime: Int       // 流访问点时间偏移
    )
    
    data class Box(
        val type: String,
        val data: ByteArray = ByteArray(0),
        val children: List<Box> = emptyList()
    )
    
    // MP4盒子写入器
    class MP4BoxWriter(private val output: OutputStream) {
        fun writeBox(box: Box) {
            // 计算总大小(包括子盒子)
            val totalSize = calculateBoxSize(box)
            
            // 写入大小(32位)
            output.writeInt(totalSize.toInt())
            
            // 写入类型(4字符)
            output.write(box.type.toByteArray())
            
            // 写入数据
            if (box.data.isNotEmpty()) {
                output.write(box.data)
            }
            
            // 写入子盒子
            box.children.forEach { child ->
                writeBox(child)
            }
        }
        
        private fun calculateBoxSize(box: Box): Long {
            var size = 8L // header大小
            
            size += box.data.size
            size += box.children.sumOf { calculateBoxSize(it) }
            
            return size
        }
    }
}

五、性能优化与最佳实践

1. 容器格式选择策略

class ContainerFormatSelector {
    
    // 根据场景选择容器格式
    fun selectFormat(requirements: PlaybackRequirements): SelectedFormat {
        return when {
            // 低延迟直播
            requirements.latency < 3000 && requirements.isLive -> {
                SelectedFormat(
                    format = ContainerFormat.FRAGMENTED_MP4,
                    protocol = StreamingProtocol.DASH_LOW_LATENCY,
                    segmentDuration = 1, // 1秒分片
                    optimization = listOf(
                        "启用分块传输编码",
                        "使用CMAF低延迟模式",
                        "启用前向纠错"
                    )
                )
            }
            
            // VOD点播
            !requirements.isLive && requirements.quality == Quality.HIGH -> {
                SelectedFormat(
                    format = ContainerFormat.MP4,
                    protocol = StreamingProtocol.PROGRESSIVE_DOWNLOAD,
                    segmentDuration = 0, // 不分片
                    optimization = listOf(
                        "moov前置优化",
                        "启用HTTP范围请求",
                        "视频轨道优先"
                    )
                )
            }
            
            // 自适应流媒体
            requirements.adaptiveBitrate -> {
                SelectedFormat(
                    format = ContainerFormat.FRAGMENTED_MP4,
                    protocol = StreamingProtocol.DASH_ADAPTIVE,
                    segmentDuration = 4, // 4秒分片
                    optimization = listOf(
                        "多码率编码",
                        "分片级索引",
                        "带宽自适应逻辑"
                    )
                )
            }
            
            // 移动端优化
            requirements.deviceType == DeviceType.MOBILE -> {
                SelectedFormat(
                    format = ContainerFormat.FRAGMENTED_MP4,
                    protocol = StreamingProtocol.HLS,
                    segmentDuration = 6, // 6秒分片
                    optimization = listOf(
                        "目标时长6秒",
                        "启用字节范围请求",
                        "移动网络优化"
                    )
                )
            }
            
            else -> {
                SelectedFormat(
                    format = ContainerFormat.FRAGMENTED_MP4,
                    protocol = StreamingProtocol.DASH,
                    segmentDuration = 2,
                    optimization = listOf("标准配置")
                )
            }
        }
    }
    
    data class PlaybackRequirements(
        val isLive: Boolean,
        val latency: Int,           // 目标延迟(毫秒)
        val adaptiveBitrate: Boolean,
        val quality: Quality,
        val deviceType: DeviceType,
        val networkType: NetworkType
    )
    
    data class SelectedFormat(
        val format: ContainerFormat,
        val protocol: StreamingProtocol,
        val segmentDuration: Int,   // 分片时长(秒)
        val optimization: List<String>
    )
    
    enum class ContainerFormat {
        MP4, FRAGMENTED_MP4, WEBM, TS
    }
    
    enum class StreamingProtocol {
        DASH, HLS, DASH_LOW_LATENCY, DASH_ADAPTIVE,
        HLS_FMP4, PROGRESSIVE_DOWNLOAD
    }
    
    enum class Quality {
        LOW, MEDIUM, HIGH, ULTRA
    }
    
    enum class DeviceType {
        MOBILE, TABLET, TV, DESKTOP
    }
    
    enum class NetworkType {
        WIFI, CELLULAR_4G, CELLULAR_5G, ETHERNET
    }
}

2. 编码与封装优化

class EncodingAndMuxingOptimizer {
    
    // GOP(Group of Pictures)结构优化
    class GOPOptimizer {
        fun optimizeGOPStructure(
            videoConfig: VideoConfig,
            useCase: UseCase
        ): GOPStructure {
            return when (useCase) {
                UseCase.VOD -> {
                    // 点播:较长的GOP,高效压缩
                    GOPStructure(
                        gopSize = 120,          // 4秒(30fps)
                        bFrames = 2,            // 适量B帧
                        closedGOP = true,       // 封闭式GOP
                        sceneChangeThreshold = 40
                    )
                }
                UseCase.LIVE -> {
                    // 直播:较短的GOP,低延迟
                    GOPStructure(
                        gopSize = 30,           // 1秒(30fps)
                        bFrames = 0,            // 无B帧以降低延迟
                        closedGOP = false,      // 开放式GOP
                        sceneChangeThreshold = 30
                    )
                }
                UseCase.LOW_LATENCY_LIVE -> {
                    // 低延迟直播:超短GOP
                    GOPStructure(
                        gopSize = 15,           // 0.5秒(30fps)
                        bFrames = 0,
                        closedGOP = false,
                        sceneChangeThreshold = 20
                    )
                }
                UseCase.ADAPTIVE_STREAMING -> {
                    // 自适应流:平衡的GOP
                    GOPStructure(
                        gopSize = 60,           // 2秒(30fps)
                        bFrames = 1,
                        closedGOP = true,
                        sceneChangeThreshold = 35
                    )
                }
            }
        }
        
        data class GOPStructure(
            val gopSize: Int,           // GOP大小(帧数)
            val bFrames: Int,           // B帧数量
            val closedGOP: Boolean,     // 是否封闭式GOP
            val sceneChangeThreshold: Int // 场景切换阈值
        )
        
        enum class UseCase {
            VOD, LIVE, LOW_LATENCY_LIVE, ADAPTIVE_STREAMING
        }
    }
    
    // 分片大小优化
    class SegmentSizeOptimizer {
        fun calculateOptimalSegmentSize(
            bitrate: Int,          // 比特率(bps)
            targetDuration: Int,   // 目标时长(秒)
            networkConditions: NetworkConditions
        ): SegmentSize {
            val targetBytes = (bitrate * targetDuration / 8).toLong()
            
            // 根据网络条件调整
            val adjustedSize = when (networkConditions.stability) {
                NetworkStability.EXCELLENT -> targetBytes
                NetworkStability.GOOD -> (targetBytes * 0.9).toLong()
                NetworkStability.FAIR -> (targetBytes * 0.8).toLong()
                NetworkStability.POOR -> (targetBytes * 0.6).toLong()
            }
            
            // 确保在合理范围内
            val minSize = 100 * 1024L  // 100KB
            val maxSize = 10 * 1024 * 1024L // 10MB
            
            val finalSize = adjustedSize.coerceIn(minSize, maxSize)
            
            return SegmentSize(
                targetBytes = finalSize,
                targetDuration = targetDuration,
                minSize = minSize,
                maxSize = maxSize,
                recommendation = generateRecommendation(finalSize, networkConditions)
            )
        }
        
        data class SegmentSize(
            val targetBytes: Long,
            val targetDuration: Int,
            val minSize: Long,
            val maxSize: Long,
            val recommendation: String
        )
        
        data class NetworkConditions(
            val bandwidth: Int,        // 带宽(bps)
            val rtt: Int,              // 往返时间(ms)
            val packetLoss: Double,    // 丢包率
            val stability: NetworkStability
        )
        
        enum class NetworkStability {
            EXCELLENT, GOOD, FAIR, POOR
        }
    }
    
    // 缓存策略优化
    class CacheOptimizer {
        fun optimizeCacheStrategy(
            format: ContainerFormat,
            segmentSize: Long,
            availableMemory: Long
        ): CacheStrategy {
            val memoryPerSegment = (availableMemory * 0.3).toLong() // 30%内存用于缓存
            
            return when (format) {
                ContainerFormat.FRAGMENTED_MP4 -> {
                    // fMP4:缓存多个分片
                    val segmentsToCache = (memoryPerSegment / segmentSize).toInt().coerceAtLeast(3)
                    CacheStrategy(
                        type = CacheType.SEGMENT_CACHE,
                        size = segmentsToCache,
                        preloadSegments = 2,
                        evictionPolicy = EvictionPolicy.LRU
                    )
                }
                ContainerFormat.MP4 -> {
                    // MP4:缓存关键数据(moov,索引)
                    CacheStrategy(
                        type = CacheType.METADATA_CACHE,
                        size = (segmentSize * 0.1).toLong().coerceAtMost(10 * 1024 * 1024),
                        preloadSegments = 0,
                        evictionPolicy = EvictionPolicy.FIFO
                    )
                }
                else -> {
                    CacheStrategy(
                        type = CacheType.SEGMENT_CACHE,
                        size = 5,
                        preloadSegments = 1,
                        evictionPolicy = EvictionPolicy.LRU
                    )
                }
            }
        }
        
        data class CacheStrategy(
            val type: CacheType,
            val size: Any,              // 缓存大小或数量
            val preloadSegments: Int,   // 预加载分片数
            val evictionPolicy: EvictionPolicy
        )
        
        enum class CacheType {
            SEGMENT_CACHE,      // 分片缓存
            METADATA_CACHE,     // 元数据缓存
            FRAME_CACHE         // 帧缓存
        }
        
        enum class EvictionPolicy {
            LRU,    // 最近最少使用
            FIFO,   // 先进先出
            LFU     // 最不经常使用
        }
    }
}

3. 兼容性与降级策略

class CompatibilityManager {
    
    // 设备兼容性检测
    fun detectContainerSupport(context: Context): ContainerSupport {
        val codecCapabilities = detectCodecCapabilities()
        val exoPlayerCapabilities = detectExoPlayerCapabilities()
        val systemCapabilities = detectSystemCapabilities(context)
        
        return ContainerSupport(
            mp4 = ContainerCapability(
                supported = true,
                h264 = codecCapabilities.h264,
                h265 = codecCapabilities.h265,
                aac = true,
                dolbyDigital = systemCapabilities.dolbyDigital
            ),
            fragmentedMp4 = ContainerCapability(
                supported = exoPlayerCapabilities.fragmentedMp4,
                h264 = codecCapabilities.h264,
                h265 = codecCapabilities.h265 && exoPlayerCapabilities.hevcDash,
                aac = true,
                dolbyDigital = false
            ),
            webm = ContainerCapability(
                supported = codecCapabilities.vp9 || codecCapabilities.av1,
                vp8 = codecCapabilities.vp8,
                vp9 = codecCapabilities.vp9,
                av1 = codecCapabilities.av1,
                opus = codecCapabilities.opus
            ),
            recommendation = generateRecommendation(
                codecCapabilities, 
                exoPlayerCapabilities, 
                systemCapabilities
            )
        )
    }
    
    // 降级策略
    fun getFallbackStrategy(
        requestedFormat: ContainerFormat,
        deviceSupport: ContainerSupport
    ): FallbackStrategy {
        return when (requestedFormat) {
            ContainerFormat.FRAGMENTED_MP4 -> {
                if (deviceSupport.fragmentedMp4.supported) {
                    // 支持fMP4,无需降级
                    FallbackStrategy(
                        targetFormat = ContainerFormat.FRAGMENTED_MP4,
                        codecFallback = getCodecFallback(
                            deviceSupport.fragmentedMp4
                        ),
                        protocolFallback = null
                    )
                } else {
                    // 不支持fMP4,降级到MP4
                    FallbackStrategy(
                        targetFormat = ContainerFormat.MP4,
                        codecFallback = getCodecFallback(deviceSupport.mp4),
                        protocolFallback = StreamingProtocol.PROGRESSIVE_DOWNLOAD
                    )
                }
            }
            ContainerFormat.WEBM -> {
                // WebM降级策略
                FallbackStrategy(
                    targetFormat = if (deviceSupport.webm.supported) {
                        ContainerFormat.WEBM
                    } else {
                        ContainerFormat.MP4
                    },
                    codecFallback = getWebmCodecFallback(deviceSupport.webm),
                    protocolFallback = null
                )
            }
            else -> {
                FallbackStrategy(
                    targetFormat = requestedFormat,
                    codecFallback = null,
                    protocolFallback = null
                )
            }
        }
    }
    
    data class ContainerSupport(
        val mp4: ContainerCapability,
        val fragmentedMp4: ContainerCapability,
        val webm: ContainerCapability,
        val recommendation: String
    )
    
    data class ContainerCapability(
        val supported: Boolean,
        val h264: Boolean = false,
        val h265: Boolean = false,
        val aac: Boolean = false,
        val dolbyDigital: Boolean = false,
        val vp8: Boolean = false,
        val vp9: Boolean = false,
        val av1: Boolean = false,
        val opus: Boolean = false
    )
    
    data class FallbackStrategy(
        val targetFormat: ContainerFormat,
        val codecFallback: CodecFallback?,
        val protocolFallback: StreamingProtocol?
    )
    
    data class CodecFallback(
        val primary: String,      // 首选编解码器
        val fallback: String,     // 降级编解码器
        val rationale: String     // 降级原因
    )
    
    // 编解码器能力检测
    private fun detectCodecCapabilities(): CodecCapabilities {
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
        val allCodecs = codecList.codecInfos
        
        return CodecCapabilities(
            h264 = allCodecs.any { it.isEncoder && it.supportsMimeType("video/avc") },
            h265 = allCodecs.any { it.isEncoder && it.supportsMimeType("video/hevc") },
            vp8 = allCodecs.any { it.isEncoder && it.supportsMimeType("video/x-vnd.on2.vp8") },
            vp9 = allCodecs.any { it.isEncoder && it.supportsMimeType("video/x-vnd.on2.vp9") },
            av1 = allCodecs.any { it.isEncoder && it.supportsMimeType("video/av01") },
            opus = allCodecs.any { it.isEncoder && it.supportsMimeType("audio/opus") }
        )
    }
    
    data class CodecCapabilities(
        val h264: Boolean,
        val h265: Boolean,
        val vp8: Boolean,
        val vp9: Boolean,
        val av1: Boolean,
        val opus: Boolean
    )
}

六、未来趋势与发展方向

1. CMAF的演进

class CMAFFutureDevelopments {
    
    // CMAF 2.0新特性
    data class CMAF20Features(
        // 低延迟增强
        val ultraLowLatency: UltraLowLatencyMode,
        
        // 新编码格式支持
        val vvcSupport: Boolean,          // VVC (H.266)
        val evcSupport: Boolean,          // EVC
        val lc3Support: Boolean,          // LC3音频
        
        // 交互功能
        val interactiveFeatures: InteractiveFeatures,
        
        // 元数据增强
        val enhancedMetadata: EnhancedMetadataSupport,
        
        // 安全性增强
        val securityFeatures: SecurityFeatures
    )
    
    data class UltraLowLatencyMode(
        val targetLatency: Int,           // 目标延迟(毫秒)
        val chunkedTransfer: Boolean,     // 分块传输
        val cmafChunk: Boolean,           // CMAF分块
        val pushMode: Boolean             // 推送模式
    )
    
    data class InteractiveFeatures(
        val branching: Boolean,           // 分支叙事
        val personalizedAds: Boolean,     // 个性化广告
        val chooseYourOwnAdventure: Boolean, // 交互式故事
        val multiEnding: Boolean          // 多结局
    )
    
    // 边缘计算与容器格式
    class EdgeComputingIntegration {
        fun optimizeForEdge(
            edgeNodeCapabilities: EdgeNodeCapabilities
        ): EdgeOptimization {
            return EdgeOptimization(
                containerFormat = if (edgeNodeCapabilities.supportsCMAF) {
                    ContainerFormat.CMAF
                } else {
                    ContainerFormat.FRAGMENTED_MP4
                },
                transcodingStrategy = if (edgeNodeCapabilities.gpuAvailable) {
                    TranscodingStrategy.GPU_ACCELERATED
                } else {
                    TranscodingStrategy.CPU_EFFICIENT
                },
                cachingStrategy = CachingStrategy(
                    type = CacheType.EDGE_CACHE,
                    ttl = edgeNodeCapabilities.cacheTtl,
                    prefetchEnabled = true
                ),
                cdnIntegration = CDNIntegration(
                    supportsChunkedTransfer = true,
                    supportsByteRange = true,
                    supportsManifestManipulation = edgeNodeCapabilities.supportsManifestManipulation
                )
            )
        }
    }
}

2. 新技术整合

class EmergingTechnologies {
    
    // AV1在容器中的支持
    class AV1InContainers {
        fun createAV1MP4Configuration(): AV1Configuration {
            return AV1Configuration(
                container = ContainerFormat.FRAGMENTED_MP4,
                codecString = "av01.0.04M.08", // AV1 Codec String
                profile = AV1Profile.MAIN,
                level = AV1Level.LEVEL_4_0,
                tier = AV1Tier.MAIN,
                colorConfig = AV1ColorConfig(
                    bitDepth = 8,
                    chromaSubsampling = ChromaSubsampling.YUV420,
                    colorSpace = ColorSpace.BT709,
                    transferCharacteristics = TransferCharacteristics.BT709,
                    matrixCoefficients = MatrixCoefficients.BT709
                ),
                optimization = listOf(
                    "使用AV1 Annex B格式",
                    "启用film grain合成",
                    "使用CDEF和LR滤波"
                )
            )
        }
        
        data class AV1Configuration(
            val container: ContainerFormat,
            val codecString: String,
            val profile: AV1Profile,
            val level: AV1Level,
            val tier: AV1Tier,
            val colorConfig: AV1ColorConfig,
            val optimization: List<String>
        )
    }
    
    // 沉浸式媒体(VR/360°)
    class ImmersiveMediaContainers {
        fun createVRMP4Configuration(): VRConfiguration {
            return VRConfiguration(
                container = ContainerFormat.FRAGMENTED_MP4,
                projection = Projection.EQUIRECTANGULAR,
                stereoMode = StereoMode.TOP_BOTTOM,
                spatialAudio = true,
                metadata = VRMetadata(
                    initialViewHeading = 0.0,
                    initialViewPitch = 0.0,
                    initialViewRoll = 0.0,
                    initialViewFov = 100.0,
                    timestampSubsamples = true
                ),
                optimization = listOf(
                    "分片级视口自适应",
                    "多分辨率编码",
                    "运动预测预加载"
                )
            )
        }
    }
    
    // 机器学习与容器优化
    class MLEnhancedContainers {
        fun applyMLOptimizations(
            playbackPattern: PlaybackPattern,
            networkConditions: NetworkConditions
        ): MLOptimization {
            val model = loadMLModel("container_optimization_model")
            
            return MLOptimization(
                segmentSizePrediction = model.predictSegmentSize(
                    bitrate = playbackPattern.averageBitrate,
                    networkStability = networkConditions.stability
                ),
                prefetchStrategy = model.predictPrefetch(
                    userBehavior = playbackPattern.seekingPattern,
                    contentType = playbackPattern.contentType
                ),
                cacheOptimization = model.optimizeCache(
                    availableMemory = playbackPattern.availableMemory,
                    playbackHistory = playbackPattern.history
                ),
                codecSelection = model.selectOptimalCodec(
                    deviceCapabilities = playbackPattern.deviceCapabilities,
                    contentComplexity = playbackPattern.contentComplexity
                )
            )
        }
    }
}

七、总结与最佳实践

✅ 容器格式选择指南

场景推荐格式分片大小关键优化适用协议
点播视频MP4不分片moov前置,HTTP范围请求渐进式下载
直播流fMP42-4秒低GOP,无B帧,分片索引DASH,HLS+fMP4
低延迟直播fMP40.5-1秒CMAF低延迟,分块传输DASH-LL,LHLS
自适应流fMP42-6秒多码率,分片对齐,带宽自适应DASH,HLS
移动端fMP44-10秒字节范围请求,网络优化HLS
超高清fMP4/MP44-8秒HEVC,HDR元数据,多层编码DASH
VR/360°fMP42-4秒视口自适应,空间音频DASH-OMAF

🎯 性能指标目标

data class PerformanceTargets(
    // 容器级指标
    val moovSizePercentage: Double = 0.01,      // moov不超过文件1%
    val fragmentOverhead: Double = 0.05,        // 分片开销不超过5%
    val seekTime: Int = 200,                    // 跳转时间<200ms
    
    // 流媒体指标
    val startupTime: Int = 1500,                // 起播时间<1.5s
    val rebufferRatio: Double = 0.01,           // 卡顿率<1%
    val bitrateSwitchTime: Int = 500,           // 码率切换<500ms
    
    // 编码效率
    val compressionRatio: Double = 100.0,       // 压缩比>100:1
    val keyFrameInterval: Int = 2,              // 关键帧间隔2秒
    
    // 兼容性
    val deviceCoverage: Double = 0.98,          // 设备覆盖率>98%
    val formatSupport: Double = 0.95            // 格式支持率>95%
)

📊 监控指标建议

class ContainerMetricsMonitor {
    
    val keyMetrics = listOf(
        Metric(
            name = "容器解析时间",
            description = "解析容器头部和索引的时间",
            unit = "ms",
            target = "< 50ms",
            alertThreshold = 100
        ),
        Metric(
            name = "分片加载延迟",
            description = "从请求到开始下载分片的时间",
            unit = "ms",
            target = "< 100ms",
            alertThreshold = 500
        ),
        Metric(
            name = "索引效率",
            description = "索引大小与媒体大小的比例",
            unit = "%",
            target = "< 3%",
            alertThreshold = 10
        ),
        Metric(
            name = "GOP对齐率",
            description = "分片边界与GOP对齐的比例",
            unit = "%",
            target = "> 95%",
            alertThreshold = 80
        ),
        Metric(
            name = "字节范围请求效率",
            description = "字节范围请求的命中率",
            unit = "%",
            target = "> 90%",
            alertThreshold = 70
        )
    )
    
    data class Metric(
        val name: String,
        val description: String,
        val unit: String,
        val target: String,
        val alertThreshold: Int
    )
}

🚀 推荐学习路径

  1. 基础掌握

    • ISO BMFF标准文档
    • MP4文件格式详解
    • ExoPlayer源码分析
  2. 进阶实践

    • 实现简单的MP4解析器
    • 构建fMP4分片生成器
    • 集成DASH/HLS播放
  3. 专业深入

    • CMAF规范研究
    • 低延迟流媒体优化
    • 容器格式性能调优
  4. 前沿探索

    • AV1/AV2容器支持
    • 沉浸式媒体容器
    • AI驱动的容器优化

实践建议: 在实际项目中,建议从理解现有的MP4文件开始,逐步深入到fMP4和流媒体协议。记得使用工具(如mp4info, bento4)分析现有文件结构,这对理解容器格式非常有帮助!

更多推荐