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。
命令行工具
mp4ff提供了一些有用的命令行工具:
mp4ff-info
- 打印MP4文件的box层次结构树mp4ff-pslister
- 提取并显示AVC或HEVC的SPS和PPSmp4ff-nallister
- 列出视频的NALU和图片类型mp4ff-subslister
- 列出wvtt或stpp字幕样本的详细信息mp4ff-crop
- 裁剪渐进式MP4文件到指定时长mp4ff-encrypt
- 使用cenc或cbcs通用加密方案加密碎片化文件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模块的主要包包括:
mp4
- 提供MP4 box的解析和写入支持avc
- 处理AVC/H.264视频hevc
- 处理HEVC视频vvc
- 处理VVC视频sei
- 处理补充增强信息(SEI)av1
- 提供AV1视频打包的基本支持aac
- 支持AAC音频bits
- 提供位和字节读写器
文件结构
mp4文件的主要结构是mp4.File
:
- 渐进式(非碎片化)文件包含
Ftyp
、Moov
和Mdat
box - 碎片化文件包含:
Init
- 包含ftyp
和moov
boxSegments
-MediaSegment
切片,包含一个或多个Fragment
Fragment
- 包含一个moof
box和一个mdat
box
性能优化
mp4ff提供了多种性能优化方式:
-
延迟模式解码 - 不立即读取
mdat
数据parsedMp4, err = mp4.DecodeFile(ifd, mp4.WithDecodeMode(mp4.DecModeLazyMdat))
-
使用
SliceReader
和SliceWriter
进行高效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
更多关于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文档来实现更复杂的功能。