golang MP4文件视频音频字幕元数据处理插件库mp4ff的使用

Golang MP4文件视频音频字幕元数据处理插件库mp4ff的使用

mp4ff是一个用于处理MP4媒体文件的Golang库,支持AVC和HEVC视频、AAC和AC-3音频、stpp和wvtt字幕以及定时元数据轨道的解析和写入。

功能特性

mp4ff模块实现了对MP4媒体文件的解析和写入功能,主要专注于用于MPEG-DASH、MSS和HLS fMP4流媒体的碎片化文件,但也可以解码和编码渐进式MP4文件所需的所有box。

Logo

命令行工具

mp4ff提供了一些有用的命令行工具:

  1. mp4ff-info - 打印MP4文件的box层次结构树
  2. mp4ff-pslister - 提取并显示AVC或HEVC的SPS和PPS
  3. mp4ff-nallister - 列出视频的NALU和图片类型
  4. mp4ff-subslister - 列出wvtt或stpp字幕样本的详细信息
  5. mp4ff-crop - 裁剪渐进式MP4文件到指定时长
  6. mp4ff-encrypt - 使用cenc或cbcs通用加密方案加密碎片化文件
  7. mp4ff-decrypt - 解密使用cenc或cbcs加密的碎片化文件

安装这些工具可以使用以下命令:

go install github.com/Eyevinn/mp4ff/cmd/mp4ff-info@latest
go install github.com/Eyevinn/mp4ff/cmd/mp4ff-encrypt@latest
...

示例代码

mp4ff提供了多种常见用例的示例代码:

1. 创建初始化片段

// 创建空初始化片段
init := mp4.CreateEmptyInit()
// 添加空轨道
init.AddEmptyTrack(timescale, mediatype, language)
// 设置HEVC描述符
init.Moov.Trak.SetHEVCDescriptor("hvc1", vpsNALUs, spsNALUs, ppsNALUs)

2. 创建媒体片段

// 创建媒体片段
seg := mp4.NewMediaSegment()
// 创建碎片
frag := mp4.CreateFragment(uint32(segNr), mp4.DefaultTrakID)
seg.AddFragment(frag)
// 添加样本
for _, sample := range samples {
    frag.AddFullSample(sample)
}
// 编码输出
err := seg.Encode(w)

3. 完整示例:创建HEVC视频片段

package main

import (
    "os"
    "github.com/Eyevinn/mp4ff/mp4"
)

func main() {
    // 1. 创建初始化片段
    init := mp4.CreateEmptyInit()
    init.AddEmptyTrack(90000, "video", "und")
    
    // HEVC参数设置
    vps := []byte{0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60}
    sps := []byte{0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03}
    pps := []byte{0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40}
    
    init.Moov.Trak.SetHEVCDescriptor("hvc1", [][]byte{vps}, [][]byte{sps}, [][]byte{pps})
    
    // 写入初始化文件
    initFile, _ := os.Create("init.mp4")
    init.Encode(initFile)
    initFile.Close()
    
    // 2. 创建媒体片段
    seg := mp4.NewMediaSegment()
    frag := mp4.CreateFragment(1, 1)
    seg.AddFragment(frag)
    
    // 添加样本
    samples := []mp4.FullSample{
        {
            Sample: mp4.Sample{
                Flags: 0x02000000, // sync sample
                Dur:   3000,
                Size:  1024,
                Cto:   0,
            },
            DecodeTime: 0,
            Data:       make([]byte, 1024), // 示例数据
        },
        {
            Sample: mp4.Sample{
                Flags: 0,
                Dur:   3000,
                Size:  768,
                Cto:   0,
            },
            DecodeTime: 3000,
            Data:       make([]byte, 768),
        },
    }
    
    for _, sample := range samples {
        frag.AddFullSample(sample)
    }
    
    // 写入媒体文件
    mediaFile, _ := os.Create("segment1.m4s")
    seg.Encode(mediaFile)
    mediaFile.Close()
}

核心包结构

mp4ff模块的主要包包括:

  1. mp4 - 提供MP4 box的解析和写入支持
  2. avc - 处理AVC/H.264视频
  3. hevc - 处理HEVC视频
  4. vvc - 处理VVC视频
  5. sei - 处理补充增强信息(SEI)
  6. av1 - 提供AV1视频打包的基本支持
  7. aac - 支持AAC音频
  8. bits - 提供位和字节读写器

文件结构

mp4文件的主要结构是mp4.File

  • 渐进式(非碎片化)文件包含FtypMoovMdat box
  • 碎片化文件包含:
    • Init - 包含ftypmoov box
    • Segments - MediaSegment切片,包含一个或多个Fragment
    • Fragment - 包含一个moof box和一个mdat box

性能优化

mp4ff提供了多种性能优化方式:

  1. 延迟模式解码 - 不立即读取mdat数据

    parsedMp4, err = mp4.DecodeFile(ifd, mp4.WithDecodeMode(mp4.DecModeLazyMdat))
    
  2. 使用SliceReaderSliceWriter进行高效IO操作

    err := seg.EncodeSW(sw)
    

贡献指南

贡献代码时请遵循Conventional Commits规范,示例:

  • feat: add support for VVC video codec
  • fix: resolve memory leak in fragment processing
  • docs: update API documentation for mp4.File
  • chore: update dependencies to latest versions

许可证

MIT许可证,详情见项目LICENSE文件。

支持

如需进一步开发、定制化或技术支持,请联系Eyevinn Technology。


更多关于golang MP4文件视频音频字幕元数据处理插件库mp4ff的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang MP4文件视频音频字幕元数据处理插件库mp4ff的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用mp4ff库处理MP4文件元数据

mp4ff是一个用于解析和操作MP4文件格式的Go语言库。它可以帮助你读取和修改MP4容器中的视频、音频、字幕轨道以及元数据信息。

安装

go get -u github.com/edgeware/mp4ff

基本用法

1. 解析MP4文件

package main

import (
	"fmt"
	"os"
	"log"
	"github.com/edgeware/mp4ff/mp4"
)

func main() {
	// 打开MP4文件
	file, err := os.Open("example.mp4")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	// 解析MP4文件
	parsedMp4, err := mp4.DecodeFile(file)
	if err != nil {
		log.Fatal(err)
	}

	// 打印基本信息
	fmt.Printf("File type: %s\n", parsedMp4.Ftyp.Name)
	fmt.Printf("Major brand: %s\n", parsedMp4.Ftyp.MajorBrand)
	fmt.Printf("Timescale: %d\n", parsedMp4.Moov.Mvhd.Timescale)
}

2. 获取视频和音频轨道信息

func printTracksInfo(parsedMp4 *mp4.File) {
	// 遍历所有轨道
	for _, trak := range parsedMp4.Moov.Traks {
		// 获取轨道类型
		handlerType := trak.Mdia.Hdlr.HandlerType
		fmt.Printf("\nTrack ID: %d, Type: %s\n", trak.Tkhd.TrackID, handlerType)

		// 视频轨道
		if handlerType == "vide" {
			width := float64(trak.Tkhd.Width) / 65536
			height := float64(trak.Tkhd.Height) / 65536
			fmt.Printf("  Video dimensions: %.2fx%.2f\n", width, height)
			
			// 获取视频编解码器信息
			if trak.Mdia.Minf.Stbl.Stsd.AvcX != nil {
				fmt.Printf("  Codec: AVC (H.264)\n")
			} else if trak.Mdia.Minf.Stbl.Stsd.HvcX != nil {
				fmt.Printf("  Codec: HEVC (H.265)\n")
			}
		}

		// 音频轨道
		if handlerType == "soun" {
			sampleRate := float64(trak.Mdia.Mdhd.Timescale)
			fmt.Printf("  Audio sample rate: %.0f Hz\n", sampleRate)
			
			// 获取音频编解码器信息
			if trak.Mdia.Minf.Stbl.Stsd.Mp4a != nil {
				fmt.Printf("  Codec: MPEG-4 Audio\n")
			}
		}

		// 字幕轨道
		if handlerType == "subt" {
			fmt.Println("  Subtitle track")
		}
	}
}

3. 读取元数据

func printMetadata(parsedMp4 *mp4.File) {
	// 检查是否有元数据盒子(udta)
	if parsedMp4.Moov.Udta != nil {
		fmt.Println("\nMetadata:")
		
		// 遍历所有元数据条目
		for _, box := range parsedMp4.Moov.Udta.Children {
			switch b := box.(type) {
			case *mp4.MetaBox:
				for _, metaChild := range b.Children {
					if ilst, ok := metaChild.(*mp4.IlstBox); ok {
						for _, entry := range ilst.Entries {
							fmt.Printf("  %s: %s\n", entry.Name, entry.ValueString())
						}
					}
				}
			}
		}
	} else {
		fmt.Println("No metadata found")
	}
}

4. 修改元数据

func addMetadata(parsedMp4 *mp4.File, title, artist string) {
	// 创建或获取udta盒子
	if parsedMp4.Moov.Udta == nil {
		parsedMp4.Moov.Udta = &mp4.UdtaBox{}
	}

	// 创建meta盒子
	meta := &mp4.MetaBox{}
	hdlr := &mp4.HdlrBox{HandlerType: "mdir", Name: "MetadataHandler"}
	meta.AddChild(hdlr)

	// 创建ilst盒子并添加元数据
	ilst := &mp4.IlstBox{}
	
	// 添加标题
	if title != "" {
		ilst.AddEntry(&mp4.MetadataEntry{
			Type: mp4.MetadataEntryString,
			Name: "©nam",
			Data: []byte(title),
		})
	}
	
	// 添加艺术家
	if artist != "" {
		ilst.AddEntry(&mp4.MetadataEntry{
			Type: mp4.MetadataEntryString,
			Name: "©ART",
			Data: []byte(artist),
		})
	}
	
	meta.AddChild(ilst)
	parsedMp4.Moov.Udta.AddChild(meta)
}

5. 保存修改后的文件

func saveModifiedFile(parsedMp4 *mp4.File, outputPath string) error {
	// 创建输出文件
	outFile, err := os.Create(outputPath)
	if err != nil {
		return err
	}
	defer outFile.Close()

	// 写入修改后的MP4
	err = parsedMp4.Encode(outFile)
	if err != nil {
		return err
	}

	return nil
}

完整示例

func main() {
	// 1. 解析MP4文件
	file, err := os.Open("input.mp4")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	parsedMp4, err := mp4.DecodeFile(file)
	if err != nil {
		log.Fatal(err)
	}

	// 2. 打印轨道信息
	printTracksInfo(parsedMp4)

	// 3. 打印现有元数据
	printMetadata(parsedMp4)

	// 4. 添加新元数据
	addMetadata(parsedMp4, "My Video", "John Doe")

	// 5. 保存修改后的文件
	err = saveModifiedFile(parsedMp4, "output.mp4")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("File saved successfully")
}

高级功能

提取视频关键帧时间戳

func printKeyFrameTimes(parsedMp4 *mp4.File) {
	for _, trak := range parsedMp4.Moov.Traks {
		if trak.Mdia.Hdlr.HandlerType == "vide" {
			stss := trak.Mdia.Minf.Stbl.Stss
			stts := trak.Mdia.Minf.Stbl.Stts
			ctts := trak.Mdia.Minf.Stbl.Ctts
			stsc := trak.Mdia.Minf.Stbl.Stsc
			stsz := trak.Mdia.Minf.Stbl.Stsz
			stco := trak.Mdia.Minf.Stbl.Stco

			if stss == nil {
				fmt.Println("No keyframe information available")
				return
			}

			timescale := trak.Mdia.Mdhd.Timescale
			fmt.Printf("Key frames (timescale: %d):\n", timescale)

			for _, sampleNum := range stss.SampleNumber {
				time, err := mp4.GetSampleTime(sampleNum, stts, ctts)
				if err != nil {
					log.Printf("Error getting time for sample %d: %v", sampleNum, err)
					continue
				}
				
				// 转换为秒
				seconds := float64(time) / float64(timescale)
				fmt.Printf("  Sample %d: %.3f seconds\n", sampleNum, seconds)
			}
		}
	}
}

mp4ff库提供了强大的功能来处理MP4文件的各个方面,包括视频、音频、字幕轨道以及元数据。你可以根据需要进一步探索其API文档来实现更复杂的功能。

回到顶部