365 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package packer
import (
"bytes"
"fmt"
"github.com/Eyevinn/mp4ff/mp4"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"log"
"os"
"os/exec"
"strings"
)
/*
// Горутина для объединения видео и аудио в контейнер MP4
go func(oldH264Filename, oldPCMFilename, oldMP4Filename string) {
// Создание MP4 файл
//if err := createMP4File(oldH264Filename, aacFilename, oldMP4Filename, h264Params); err != nil {
// log.Printf("Ошибка создания MP4: %v", err)
//} else {
// log.Printf("Файлы %s, %s успешно объединены в контейнер MP4: %s\n",
// oldH264Filename, aacFilename, oldMP4Filename)
//}
// Часть программы, вызывающая ffmpeg для объединения видеофайла .h264 аудиофайла .pcm в контейнер .mp4 -
// остается в программе до реализации аналогичного функционала без использования ffmpeg.
// Передаем значения переменных в замыкание, чтобы избежать их изменения
muxedFilename := strings.Replace(oldH264Filename, ".h264", ".mp4", 1)
cmd := exec.Command("ffmpeg",
"-y",
"-fflags", "+genpts",
"-r", "25",
"-i", oldH264Filename,
"-i", aacFilename,
"-c:v", "copy",
muxedFilename,
)
if err := cmd.Run(); err != nil {
log.Printf("Ошибка при объединении фрагментов: %v", err)
} else {
log.Printf("Фрагменты объединены в файл %s", muxedFilename)
}
// Удаление временных файлов
//os.Remove(oldH264Filename)
//os.Remove(aacFilename)
//os.Remove(oldPCMFilename)
log.Printf("Файлы %s, %s, %s успешно удалены\n", oldH264Filename, aacFilename, oldPCMFilename)
}(oldH264Filename, oldPCMFilename, oldMP4Filename)
*/
// createMP4FileFfmpeg takes H264 and AAC files to make MP4 file using ffmpeg.
func createMP4FileFfmpeg(H264Filename, aacFilename string, newTimestamp string) error {
muxedFilename := strings.Replace(H264Filename, ".h264", ".mp4", 1)
cmd := exec.Command("ffmpeg",
"-y",
"-fflags", "+genpts",
"-r", "25",
"-i", H264Filename,
"-i", aacFilename,
"-c:v", "copy",
muxedFilename,
)
if err := cmd.Run(); err != nil {
return fmt.Errorf("combining fragments error: %v", err)
}
return nil
}
// Sample единый тип для описания сэмпла (для видео и аудио)
type Sample struct {
Data []byte
Offset uint64
Size uint32
Dur uint32
IsSync bool
}
// createMP4File packs H264 and AAC to MP4.
func createMP4File(videoFile, audioFile, outputFile string, formaH264 *format.H264) error {
videoData, err := os.ReadFile(videoFile)
if err != nil {
return err
}
videoSamples := parseH264Samples(videoData)
audioData, err := os.ReadFile(audioFile)
if err != nil {
return err
}
audioSamples := parseAACSamples(audioData)
f := mp4.NewFile()
videoTrack := createVideoTrack(formaH264.SPS, formaH264.PPS)
audioTrack := createAudioTrack(8000, 1)
addSamplesToVideoTrack(videoTrack, videoSamples)
addSamplesToAudioTrack(audioTrack, audioSamples, 8000)
moov := mp4.NewMoovBox()
moov.AddChild(videoTrack)
moov.AddChild(audioTrack)
f.AddChild(moov, 0)
mdat := mp4.MdatBox{}
mdat.Data = append(videoData, audioData...)
f.AddChild(&mdat, 0)
outFile, err := os.Create(outputFile)
if err != nil {
return err
}
defer outFile.Close()
return f.Encode(outFile)
}
// createVideoTrack создаёт видео-трек на основе SPS и PPS.
func createVideoTrack(sps, pps []byte) *mp4.TrakBox {
sps = stripAnnexB(sps)
pps = stripAnnexB(pps)
if len(sps) < 4 {
log.Fatalln("SPS слишком короткий или пустой")
}
if len(pps) < 1 {
log.Fatalln("PPS слишком короткий или пустой")
}
trak := mp4.NewTrakBox()
timescale := uint32(90000)
mdhd := &mp4.MdhdBox{
Timescale: timescale,
Language: convertLanguage("und"),
}
hdlr := &mp4.HdlrBox{
HandlerType: "vide",
Name: "VideoHandler",
}
avc1 := mp4.NewVisualSampleEntryBox("avc1")
avc1.Width = 1280
avc1.Height = 720
avcC, err := mp4.CreateAvcC([][]byte{sps}, [][]byte{pps}, true)
if err != nil {
log.Fatalf("ошибка создания avcC: %v", err)
}
avcC.AVCLevelIndication = 0x1f
avc1.AddChild(avcC)
stbl := mp4.NewStblBox()
stsd := mp4.NewStsdBox()
stsd.AddChild(avc1)
stbl.AddChild(stsd)
stbl.AddChild(&mp4.SttsBox{})
stbl.AddChild(&mp4.StscBox{})
stbl.AddChild(&mp4.StszBox{})
stbl.AddChild(&mp4.StcoBox{})
trak.Mdia = &mp4.MdiaBox{
Mdhd: mdhd,
Hdlr: hdlr,
Minf: &mp4.MinfBox{
Stbl: stbl,
},
}
return trak
}
// stripAnnexB удаляет стартовый код Annex-B из NAL-единицы.
func stripAnnexB(nalu []byte) []byte {
if len(nalu) >= 4 && bytes.Equal(nalu[:4], []byte{0x00, 0x00, 0x00, 0x01}) {
return nalu[4:]
}
if len(nalu) >= 3 && bytes.Equal(nalu[:3], []byte{0x00, 0x00, 0x01}) {
return nalu[3:]
}
return nalu
}
// createAudioTrack создаёт аудио-трек.
func createAudioTrack(sampleRate, channels int) *mp4.TrakBox {
trak := mp4.NewTrakBox()
timescale := uint32(sampleRate)
mdhd := &mp4.MdhdBox{
Timescale: timescale,
Language: convertLanguage("und"),
}
hdlr := &mp4.HdlrBox{
HandlerType: "soun",
Name: "SoundHandler",
}
mp4a := mp4.NewAudioSampleEntryBox("mp4a")
mp4a.ChannelCount = uint16(channels)
mp4a.SampleRate = uint16(sampleRate)
mp4a.SampleSize = 16
asc := getAACConfig(sampleRate, channels)
esds := createESDSBox(asc)
mp4a.AddChild(esds)
stbl := mp4.NewStblBox()
stsd := mp4.NewStsdBox()
stsd.AddChild(mp4a)
stbl.AddChild(stsd)
stbl.AddChild(&mp4.SttsBox{})
stbl.AddChild(&mp4.StscBox{})
stbl.AddChild(&mp4.StszBox{})
stbl.AddChild(&mp4.StcoBox{})
trak.Mdia = &mp4.MdiaBox{
Mdhd: mdhd,
Hdlr: hdlr,
Minf: &mp4.MinfBox{
Stbl: stbl,
},
}
return trak
}
// createESDSBox создаёт ESDS-бокс для AAC.
func createESDSBox(asc []byte) *mp4.EsdsBox {
return &mp4.EsdsBox{
ESDescriptor: mp4.ESDescriptor{
DecConfigDescriptor: &mp4.DecoderConfigDescriptor{
ObjectType: 0x40,
StreamType: 0x05,
DecSpecificInfo: &mp4.DecSpecificInfoDescriptor{
DecConfig: asc,
},
},
},
}
}
func getAACConfig(sampleRate, channels int) []byte {
// Пример конфигурации для 8000 Гц, моно
return []byte{0x12, 0x10}
}
// addSamplesToVideoTrack заполняет таблицы сэмплов видео-трека.
func addSamplesToVideoTrack(trak *mp4.TrakBox, samples []Sample) {
stbl := trak.Mdia.Minf.Stbl
stts := mp4.SttsBox{}
for _, s := range samples {
stts.SampleCount = []uint32{1}
stts.SampleTimeDelta = []uint32{s.Dur}
}
stsz := mp4.StszBox{}
for _, s := range samples {
stsz.SampleSize = []uint32{s.Size}
}
stss := mp4.StssBox{}
for i, s := range samples {
if s.IsSync {
stss.SampleNumber = []uint32{uint32(i + 1)}
}
}
stbl.AddChild(&stts)
stbl.AddChild(&stsz)
stbl.AddChild(&stss)
}
// addSamplesToAudioTrack заполняет таблицы сэмплов аудио-трека.
func addSamplesToAudioTrack(trak *mp4.TrakBox, samples []Sample, sampleRate int) {
stbl := trak.Mdia.Minf.Stbl
stts := mp4.SttsBox{}
for _, s := range samples {
stts.SampleCount = []uint32{1}
stts.SampleTimeDelta = []uint32{s.Dur}
}
stsz := mp4.StszBox{}
for _, s := range samples {
stsz.SampleSize = []uint32{uint32(len(s.Data))}
}
stbl.AddChild(&stts)
stbl.AddChild(&stsz)
}
// parseH264Samples парсит H.264 поток на сэмплы.
func parseH264Samples(data []byte) []Sample {
var samples []Sample
start := 0
for i := 0; i < len(data)-3; i++ {
if bytes.Equal(data[i:i+4], []byte{0x00, 0x00, 0x00, 0x01}) {
if start != 0 {
sampleData := data[start:i]
samples = append(samples, Sample{
Size: uint32(len(sampleData)),
Dur: 3600,
IsSync: isKeyFrame(sampleData),
})
}
start = i + 4
}
}
if start < len(data) {
sampleData := data[start:]
samples = append(samples, Sample{
Size: uint32(len(sampleData)),
Dur: 3600,
IsSync: isKeyFrame(sampleData),
})
}
return samples
}
// isKeyFrame определяет, является ли NALU ключевым (IDR).
func isKeyFrame(nalu []byte) bool {
if len(nalu) == 0 {
return false
}
nalType := nalu[0] & 0x1F
return nalType == 5
}
// parseAACSamples парсит AAC данные и возвращает сэмплы.
func parseAACSamples(data []byte) []Sample {
var samples []Sample
frameSize := 1024 // примерный размер AAC-фрейма
for i := 0; i < len(data); i += frameSize {
end := i + frameSize
if end > len(data) {
end = len(data)
}
samples = append(samples, Sample{
Data: data[i:end],
Dur: 1024,
Size: uint32(end - i),
})
}
return samples
}
// convertLanguage преобразует строку языка в 16-битное значение.
func convertLanguage(lang string) uint16 {
if len(lang) != 3 {
return 0x55C4
}
b1 := lang[0] - 0x60
b2 := lang[1] - 0x60
b3 := lang[2] - 0x60
return uint16((b1 << 10) | (b2 << 5) | b3)
}