package main import ( "errors" "fmt" "github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" "github.com/pion/rtp" "log" "os" "os/exec" "strings" "sync" "time" ) var ( currentVideoFile *os.File currentAudioFile *os.File fileMu sync.Mutex ) func main() { // Парсинг URL RTSP u, err := base.ParseURL("rtsp://intercom-video-2.insit.ru/dp-ohusuxzcvzsnpzzvkpyhddnwxuyeyc") if err != nil { log.Fatalln("Ошибка парсинга URL:", err) } // Инициализация клиента и подключение к серверу client := gortsplib.Client{} err = client.Start(u.Scheme, u.Host) if err != nil { log.Fatalln("Ошибка соединения:", err) } defer client.Close() // Получение описания desc, _, err := client.Describe(u) if err != nil { log.Fatalln("Ошибка получения описания:", err) } // Установка настроек err = client.SetupAll(desc.BaseURL, desc.Medias) if err != nil { log.Fatalln("Ошибка настройки:", err) } // Определение формата видео- и аудио-потоков var h264Format *format.H264 var g711Format *format.G711 for _, media := range desc.Medias { switch media.Formats[0].(type) { case *format.H264: h264Format = media.Formats[0].(*format.H264) case *format.G711: g711Format = media.Formats[0].(*format.G711) } } if h264Format == nil || g711Format == nil { log.Fatalln("Форматы не найдены") } // Создание декодеров для видео- и аудио-потоков decoderH264, err := h264Format.CreateDecoder() if err != nil { log.Fatalln("Ошибка создания декодера H264:", err) } decoderG711, err := g711Format.CreateDecoder() if err != nil { log.Fatalln("Ошибка создания декодера G711:", err) } // Обработка RTP-пакетов: для видео – запись NAL-ов с префиксом, для аудио – запись PCM-данных. client.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) { switch forma.(type) { case *format.H264: 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 { for _, nalu := range nalus { if _, err := currentVideoFile.Write([]byte{0x00, 0x00, 0x00, 0x01}); err != nil { log.Printf("Ошибка записи префикса: %v", err) } if _, err := currentVideoFile.Write(nalu); err != nil { log.Printf("Ошибка записи NALU: %v", err) } } } case *format.G711: samples, err := decoderG711.Decode(pkt) if err != nil { log.Printf("Ошибка декодирования G711: %v", err) return } fileMu.Lock() defer fileMu.Unlock() if currentAudioFile != nil { if _, err := currentAudioFile.Write(samples); err != nil { log.Printf("Ошибка записи аудио: %v", err) } } } }) // Запуск воспроизведения _, err = client.Play(nil) if err != nil { 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) time.Sleep(waitDuration) // Создаем начальные файлы initialTimestamp := time.Now().Format("15-04_02-01-2006") videoFilename := initialTimestamp + ".h264" audioFilename := initialTimestamp + ".pcm" fileVideo, err := os.Create(videoFilename) if err != nil { log.Fatalln("Ошибка создания видеофайла:", err) } fileAudio, err := os.Create(audioFilename) 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) } fileMu.Lock() currentVideoFile = fileVideo currentAudioFile = fileAudio 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" newVideoFile, err := os.Create(newVideoFilename) if err != nil { log.Printf("Ошибка создания видеофайла для фрагмента %d: %v", newTimestamp, err) continue } newAudioFile, err := os.Create(newAudioFilename) if err != nil { log.Printf("Ошибка создания аудиофайла для фрагмента %d: %v", newTimestamp, err) newVideoFile.Close() 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) } // Переключаемся на новые файлы fileMu.Lock() oldVideoFile := currentVideoFile oldAudioFile := currentAudioFile oldVideoFilename := videoFilename oldAudioFilename := audioFilename currentVideoFile = newVideoFile currentAudioFile = newAudioFile // Обновляем имена для следующей итерации videoFilename = newVideoFilename audioFilename = newAudioFilename fileMu.Unlock() // Закрываем старые файлы и запускаем обработку (слияние с аудио) в отдельной горутине oldVideoFile.Close() oldAudioFile.Close() // Передаем значения переменных в замыкание, чтобы избежать их изменения 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) } }() var wg sync.WaitGroup wg.Add(1) wg.Wait() }