From e2ab953f8a45bb12463a8d11f26c8c8c61e27d73 Mon Sep 17 00:00:00 2001 From: Sergey Petrov Date: Wed, 26 Feb 2025 14:52:00 +0500 Subject: [PATCH] recording video is corrected --- writer/cmd/main.go | 574 ++++++++++++++++++++++++++++++++++++--------- writer/go.mod | 3 +- writer/go.sum | 5 +- 3 files changed, 463 insertions(+), 119 deletions(-) diff --git a/writer/cmd/main.go b/writer/cmd/main.go index 3f20d6b..bfefd62 100644 --- a/writer/cmd/main.go +++ b/writer/cmd/main.go @@ -1,14 +1,15 @@ package main import ( + "bytes" "errors" - "fmt" "log" "os" "strings" "sync" "time" + "github.com/Eyevinn/mp4ff/mp4" "github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/description" @@ -20,19 +21,24 @@ import ( ) var ( - currentVideoFile *os.File - currentAudioFile *os.File - fileMu sync.Mutex + currentVideoFile *os.File + currentAudioFile *os.File + currentMP4File *os.File + wg sync.WaitGroup + fileMu sync.Mutex + formaH264Mu sync.Mutex + formaH264 *format.H264 + currentSegmentStarted bool // Флаг указывает записан ли уже IDR (с SPS/PPS) в текущий сегмент ) func main() { // Парсинг URL RTSP - u, err := base.ParseURL("rtsp://intercom-video-2.insit.ru/dp-ohusuxzcvzsnpzzvkpyhddnwxuyeyc") + u, err := base.ParseURL("rtsp://intercom-video-1.insit.ru/dp-gfahybswjoendkcpbaqvgkeizruudbhsfdr") if err != nil { log.Fatalln("Ошибка парсинга URL:", err) } - // Инициализация клиента и подключение к серверу + // Инициализация клиента и подключение к камере client := gortsplib.Client{} err = client.Start(u.Scheme, u.Host) if err != nil { @@ -52,7 +58,7 @@ func main() { log.Fatalln("Ошибка настройки:", err) } - // Определение формата видео- и аудио-потоков + // Проверка форматов для видео- и аудио-потоков var h264Format *format.H264 var g711Format *format.G711 for _, media := range desc.Medias { @@ -63,57 +69,96 @@ func main() { g711Format = media.Formats[0].(*format.G711) } } - if h264Format == nil || g711Format == nil { log.Fatalln("Форматы не найдены") } - // Создание декодеров для видео- и аудио-потоков + // Создание декодера для видео-потока H264 decoderH264, err := h264Format.CreateDecoder() if err != nil { log.Fatalln("Ошибка создания декодера H264:", err) } - // Обработка RTP-пакетов: для видео – запись NAL-ов с префиксом, для аудио – запись PCM-данных. + // Обработка RTP-пакетов: для видео – сбор NAL-ов и запись в файл с префиксом, для аудио – запись PCM-данных. client.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) { - switch forma.(type) { + switch f := forma.(type) { case *format.H264: + formaH264Mu.Lock() + formaH264 = f + formaH264Mu.Unlock() + nalus, err := decoderH264.Decode(pkt) if err != nil && !errors.Is(err, rtph264.ErrMorePacketsNeeded) { log.Printf("Ошибка декодирования H264: %v", err) return } + fileMu.Lock() - defer fileMu.Unlock() if currentVideoFile != nil { + // Если сегмент ещё не начат, ожидается появление ключевого кадра (IDR). + if !currentSegmentStarted { + var isIDR bool + for _, nalu := range nalus { + if len(nalu) > 0 && (nalu[0]&0x1F) == 5 { + isIDR = true + break + } + } + if !isIDR { + // Пакет не записывается, если в нём нет ключевого кадра. + fileMu.Unlock() + return + } + // При получении IDR происходит вставка SPS и PPS (с префиксами) в начало сегмента. + if len(f.SPS) > 0 { + if _, err := currentVideoFile.Write([]byte{0x00, 0x00, 0x00, 0x01}); err != nil { + log.Printf("Ошибка записи стартового кода SPS: %v", err) + } + if _, err := currentVideoFile.Write(f.SPS); err != nil { + log.Printf("Ошибка записи SPS: %v", err) + } + } + if len(f.PPS) > 0 { + if _, err := currentVideoFile.Write([]byte{0x00, 0x00, 0x00, 0x01}); err != nil { + log.Printf("Ошибка записи стартового кода PPS: %v", err) + } + if _, err := currentVideoFile.Write(f.PPS); err != nil { + log.Printf("Ошибка записи PPS: %v", err) + } + } + currentSegmentStarted = true + } + // Запись каждой NAL-единицы с префиксом for _, nalu := range nalus { if _, err := currentVideoFile.Write([]byte{0x00, 0x00, 0x00, 0x01}); err != nil { - log.Printf("Ошибка записи префикса: %v", err) + log.Printf("Ошибка записи стартового кода NALU: %v", err) } if _, err := currentVideoFile.Write(nalu); err != nil { log.Printf("Ошибка записи NALU: %v", err) } } } + fileMu.Unlock() + case *format.G711: if strings.Contains(forma.RTPMap(), "PCMA") { sampleG711a := g711.DecodeAlaw(pkt.Payload) - defer fileMu.Lock() - defer fileMu.Unlock() + fileMu.Lock() if currentAudioFile != nil { if _, err := currentAudioFile.Write(sampleG711a); err != nil { log.Printf("Ошибка записи аудио: %v", err) } } + fileMu.Unlock() } else if strings.Contains(forma.RTPMap(), "PCMU") { sampleG711u := g711.DecodeUlaw(pkt.Payload) fileMu.Lock() - defer fileMu.Unlock() if currentAudioFile != nil { if _, err := currentAudioFile.Write(sampleG711u); err != nil { log.Printf("Ошибка записи аудио: %v", err) } } + fileMu.Unlock() } else { log.Println("Аудиокодек не идентифицирован") } @@ -126,21 +171,22 @@ func main() { log.Fatalln("Ошибка запуска воспроизведения:", err) } - // Параметры записи + // Параметр записи period := time.Hour // Ожидание начала следующего часа now := time.Now() - nextHour := now.Truncate(time.Hour).Add(time.Hour) - waitDuration := nextHour.Sub(now) - fmt.Printf("Ожидание до начала записи: %v\n", waitDuration) + nextSegment := now.Truncate(time.Hour).Add(time.Hour) + waitDuration := nextSegment.Sub(now) + log.Printf("Ожидание до начала записи: %v\n", waitDuration) time.Sleep(waitDuration) log.Println("Начало записи фрагмента") - // Создаем начальные файлы + // Создание начальных файлов initialTimestamp := time.Now().Format("15-04_02-01-2006") videoFilename := initialTimestamp + ".h264" audioFilename := initialTimestamp + ".pcm" + mp4Filename := initialTimestamp + ".mp4" fileVideo, err := os.Create(videoFilename) if err != nil { @@ -150,150 +196,446 @@ func main() { if err != nil { log.Fatalln("Ошибка создания аудиофайла:", err) } - // Записываем SPS и PPS в начале видеофайла, если они заданы - if len(h264Format.SPS) > 0 { - fileVideo.Write([]byte{0x00, 0x00, 0x00, 0x01}) - fileVideo.Write(h264Format.SPS) - } - if len(h264Format.PPS) > 0 { - fileVideo.Write([]byte{0x00, 0x00, 0x00, 0x01}) - fileVideo.Write(h264Format.PPS) + fileMP4, err := os.Create(mp4Filename) + if err != nil { + log.Fatalln("Ошибка создания mp4 файла:", err) } + fileMu.Lock() currentVideoFile = fileVideo currentAudioFile = fileAudio + currentMP4File = fileMP4 + + // Начало нового сегмента – флаг сбрасывается и ожидается первый IDR + currentSegmentStarted = false fileMu.Unlock() - // Используем тикер для периодической ротации файлов без остановки записи + // Тикер для периодической ротации файлов ticker := time.NewTicker(period) defer ticker.Stop() - // Горутина, отвечающая за смену файлов + // Горутина для смены файлов go func() { for range ticker.C { - // Открываем новые файлы заранее, чтобы запись шла непрерывно newTimestamp := time.Now().Format("15-04_02-01-2006") newVideoFilename := newTimestamp + ".h264" newAudioFilename := newTimestamp + ".pcm" + newMP4Filename := newTimestamp + ".mp4" newVideoFile, err := os.Create(newVideoFilename) if err != nil { - log.Printf("Ошибка создания видеофайла для фрагмента %d: %v", newTimestamp, err) + log.Printf("Ошибка создания видеофайла для фрагмента %s: %v", newTimestamp, err) continue } newAudioFile, err := os.Create(newAudioFilename) if err != nil { - log.Printf("Ошибка создания аудиофайла для фрагмента %d: %v", newTimestamp, err) - newVideoFile.Close() + log.Printf("Ошибка создания аудиофайла для фрагмента %s: %v", newTimestamp, err) continue } - // Записываем SPS/PPS в новый видеофайл - if len(h264Format.SPS) > 0 { - newVideoFile.Write([]byte{0x00, 0x00, 0x00, 0x01}) - newVideoFile.Write(h264Format.SPS) - } - if len(h264Format.PPS) > 0 { - newVideoFile.Write([]byte{0x00, 0x00, 0x00, 0x01}) - newVideoFile.Write(h264Format.PPS) + newMP4File, err := os.Create(newMP4Filename) + if err != nil { + log.Printf("Ошибка создания mp4 файла для фрагмента %s: %v", newTimestamp, err) } - // Переключаемся на новые файлы + // Переключение на новые файлы fileMu.Lock() oldVideoFile := currentVideoFile oldAudioFile := currentAudioFile + oldMP4File := currentMP4File + + oldVideoFilename := videoFilename oldAudioFilename := audioFilename + oldMP4Filename := mp4Filename currentVideoFile = newVideoFile currentAudioFile = newAudioFile - // Обновляем имена для следующей итерации + currentMP4File = newMP4File + + // Новый сегмент ещё не начат – флаг сбрасывается и ожидается первый IDR + currentSegmentStarted = false + + // Обновление имен для следующей итерации videoFilename = newVideoFilename audioFilename = newAudioFilename + mp4Filename = newMP4Filename fileMu.Unlock() - // Закрываем старые файлы и запускаем обработку (слияние с аудио) в отдельной горутине + // Закрытие старых файлов oldVideoFile.Close() oldAudioFile.Close() + oldMP4File.Close() - // Часть программы, вызывающая ffmpeg для объединения видеофайла .h264 аудиофайла .pcm в контейнер .mp4 - - // остается в программе до реализации аналогичного функционала без использования ffmpeg. - /* - // Передаем значения переменных в замыкание, чтобы избежать их изменения - go func(vFilename, aFilename string, newTimestamp string) { - muxedFilename := strings.Replace(vFilename, ".h264", ".mp4", 1) - cmd := exec.Command("ffmpeg", - "-y", - "-fflags", "+genpts", - "-r", "25", - "-f", "h264", "-i", vFilename, - "-f", "s16le", "-ar", "8000", "-ac", "1", "-i", aFilename, - "-filter_complex", "[1:a]atempo=0.5[aud]", - "-map", "0:v", - "-map", "[aud]", - "-c:v", "copy", - "-bsf:v", "dump_extra", - "-c:a", "aac", - muxedFilename, - ) - if err := cmd.Run(); err != nil { - log.Printf("Ошибка при объединении фрагментов: %v", err) - } else { - log.Printf("Фрагменты объединены в файл %s", muxedFilename) - } - }(oldVideoFilename, oldAudioFilename, newTimestamp) - */ + log.Println("Созданы новые файлы для записи:", videoFilename, audioFilename, mp4Filename) - // Создание AAC аудиофайла из PCM аудиофайла и удаление PCM аудиофайла после успешной конвертации. - go func(aFilename string) { - // Создание файла AAC - muxedFilename := strings.Replace(aFilename, ".pcm", ".aac", 1) - audioFileACC, err := os.Create(muxedFilename) - if err != nil { - log.Println("Ошибка создания файла AAC:", err) - return - } - defer audioFileACC.Close() + // Горутина для объединения видео и аудио в контейнер MP4 + go func(oldVideoFilename, oldAudioFilename, oldMP4Filename string) { + formaH264Mu.Lock() + h264Params := formaH264 + formaH264Mu.Unlock() - // Открытие PCM файла - audioFilename, err := os.Open(aFilename) - if err != nil { - log.Println("Ошибка открытия аудиофайла PCM:", err) - return - } - defer audioFilename.Close() - - // Создание инкодера AAC - opts := &aac.Options{ - SampleRate: 8000, - NumChannels: 1, - } - - encoderAAC, err := aac.NewEncoder(audioFileACC, opts) - if err != nil { - log.Println("Ошибка создания инкодера AAC:", err) - return - } - defer encoderAAC.Close() - - // Запись аудиофайла AAC - err = encoderAAC.Encode(audioFilename) - if err != nil { - log.Println("Ошибка инкодирования в AAC:", err) + // Конвертация PCM в AAC + aacFilename := strings.Replace(oldAudioFilename, ".pcm", ".aac", 1) + if err := convertPCMtoAAC(oldAudioFilename, aacFilename); err != nil { + log.Printf("Ошибка конвертации аудио: %v", err) return } - log.Println("Аудиофайл PCM конвертирован в аудиофайл ACC успешно") + log.Printf("Файл %s успешно конвертирован в %s\n", oldAudioFilename, aacFilename) - // Удаление PCM аудиофайла - err = os.Remove(aFilename) - if err != nil { - log.Println("Ошибка удаления PCM файла:", err) + // Создание MP4 файл + if err := createMP4File(oldVideoFilename, aacFilename, oldMP4Filename, h264Params); err != nil { + log.Printf("Ошибка создания MP4: %v", err) } else { - log.Printf("PCM файл %s успешно удалён", aFilename) + log.Printf("Файлы %s, %s успешно объединены в контейнер MP4: %s\n", + oldVideoFilename, aacFilename, oldMP4Filename) } - }(oldAudioFilename) + + // Удаление временных файлов + os.Remove(oldVideoFilename) + os.Remove(aacFilename) + os.Remove(oldAudioFilename) + + log.Printf("Файлы %s, %s, %s успешно удалены\n", oldVideoFilename, aacFilename, oldAudioFilename) + }(oldVideoFilename, oldAudioFilename, oldMP4Filename) } }() - var wg sync.WaitGroup + wg.Add(1) wg.Wait() } + +// Sample – единый тип для описания сэмпла (для видео и аудио) +type Sample struct { + Data []byte + Offset uint64 + Size uint32 + Dur uint32 + IsSync bool +} + +// convertPCMtoAAC конвертирует PCM-файл в AAC-файл. +func convertPCMtoAAC(aFilename, outputFile string) error { + if outputFile == "" { + outputFile = strings.Replace(aFilename, ".pcm", ".aac", 1) + } + + audioFileAAC, err := os.Create(outputFile) + if err != nil { + log.Println("Ошибка создания файла AAC:", err) + return err + } + defer audioFileAAC.Close() + + audioFilePCM, err := os.Open(aFilename) + if err != nil { + log.Println("Ошибка открытия PCM файла:", err) + return err + } + defer audioFilePCM.Close() + + opts := &aac.Options{ + SampleRate: 8000, + NumChannels: 1, + } + + encoderAAC, err := aac.NewEncoder(audioFileAAC, opts) + if err != nil { + log.Println("Ошибка создания инкодера AAC:", err) + return err + } + defer encoderAAC.Close() + + if err = encoderAAC.Encode(audioFilePCM); err != nil { + log.Println("Ошибка инкодирования в AAC:", err) + return err + } + + if err = os.Remove(aFilename); err != nil { + log.Println("Ошибка удаления PCM файла:", err) + } else { + log.Printf("PCM файл %s успешно удалён.", aFilename) + } + return nil +} + +// createMP4File упаковывает видео (H.264) и аудио (AAC) файлы в 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) +} diff --git a/writer/go.mod b/writer/go.mod index 6d25535..4fb34f9 100644 --- a/writer/go.mod +++ b/writer/go.mod @@ -3,9 +3,11 @@ module writer go 1.23.6 require ( + github.com/Eyevinn/mp4ff v0.47.0 github.com/bluenviron/gortsplib/v4 v4.12.3 github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21 github.com/pion/rtp v1.8.11 + github.com/zaf/g711 v1.4.0 ) require ( @@ -14,7 +16,6 @@ require ( github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.15 // indirect github.com/pion/sdp/v3 v3.0.10 // indirect - github.com/zaf/g711 v1.4.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect ) diff --git a/writer/go.sum b/writer/go.sum index 8d1ecb3..a84b75b 100644 --- a/writer/go.sum +++ b/writer/go.sum @@ -1,3 +1,5 @@ +github.com/Eyevinn/mp4ff v0.47.0 h1:XSSHYt5+I0fyOnHWoNwM72DtivlmHFR0V9azgIi+ZVU= +github.com/Eyevinn/mp4ff v0.47.0/go.mod h1:hJNUUqOBryLAzUW9wpCJyw2HaI+TCd2rUPhafoS5lgg= github.com/bluenviron/gortsplib/v4 v4.12.3 h1:3EzbyGb5+MIOJQYiWytRegFEP4EW5paiyTrscQj63WE= github.com/bluenviron/gortsplib/v4 v4.12.3/go.mod h1:SkZPdaMNr+IvHt2PKRjUXxZN6FDutmSZn4eT0GmF0sk= github.com/bluenviron/mediacommon v1.14.0 h1:lWCwOBKNKgqmspRpwpvvg3CidYm+XOc2+z/Jw7LM5dQ= @@ -6,6 +8,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21 h1:yfrARW/aVlqKORCdKrYdU0PZUKPqQvYEUQBKfVlNa9Q= github.com/gen2brain/aac-go v0.0.0-20230119102159-ef1e76509d21/go.mod h1:HZqGD/LXHB1VCGUGNzuyxSsD12f3KjbJbvImAmoK/mM= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= @@ -24,8 +27,6 @@ github.com/youpy/go-riff v0.1.0 h1:vZO/37nI4tIET8tQI0Qn0Y79qQh99aEpponTPiPut7k= github.com/youpy/go-riff v0.1.0/go.mod h1:83nxdDV4Z9RzrTut9losK7ve4hUnxUR8ASSz4BsKXwQ= github.com/youpy/go-wav v0.3.2 h1:NLM8L/7yZ0Bntadw/0h95OyUsen+DQIVf9gay+SUsMU= github.com/youpy/go-wav v0.3.2/go.mod h1:0FCieAXAeSdcxFfwLpRuEo0PFmAoc+8NU34h7TUvk50= -github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b h1:QqixIpc5WFIqTLxB3Hq8qs0qImAgBdq0p6rq2Qdl634= -github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b/go.mod h1:T2h1zV50R/q0CVYnsQOQ6L7P4a2ZxH47ixWcMXFGyx8= github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c= github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=