238 lines
7.6 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 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()
}