Documentation
¶
Overview ¶
Package transcoding provides hardware-aware video transcoding for stored recordings.
It supports converting between H.264, H.265, and MJPEG formats using FFmpeg as the backend transcoder. The package performs automatic hardware capability detection on startup, checking for available encoders (V4L2M2M, VAAPI, NVENC) and falling back to software encoding (libx264/libx265) when appropriate.
Key features:
- Hardware capability self-check (detects available encoders, cores, memory)
- Async task queue with progress tracking
- Multi-format support (H.264 ↔ H.265, MJPEG → H.264)
- Per-camera transcoding configuration
- FFmpeg download management (static binary for ARM)
Architecture overview:
Manager (singleton) → Hardware probe → Task queue → Worker pool → FFmpeg exec
↕ ↕
REST API handler DB (SQLite)
The Manager coordinates probe results, queues tasks, and manages worker goroutines. Each worker invokes FFmpeg as a subprocess with appropriate encoder flags based on the hardware capabilities detected at startup.
Module path: github.com/Mi-Bee-Studio/MiBeeNvr
Index ¶
- func BuildFFmpegCommand(opts TranscodeOptions, caps HardwareCapabilities) ([]string, error)
- func CheckSufficient(caps *HardwareCapabilities, requiredCodec string) (bool, string)
- func CleanOrphanedTranscodes(ctx context.Context, dataDir string, db DBTaskLister) error
- func GetDisabledReason() string
- func ResetProbe()
- func SetDisabledReason(reason string)
- type CompletionFunc
- type DBTaskLister
- type DownloadState
- type DownloadStatus
- type Downloader
- type EncoderType
- type HardwareCapabilities
- type ManagerConfig
- type ManagerStatus
- type MediaInfo
- type QueueAPI
- type QueueConfig
- type TranscodeManager
- func (m *TranscodeManager) Downloader() *Downloader
- func (m *TranscodeManager) EnqueueRecording(cameraID, recordingID, inputPath, inputFormat string, ...) error
- func (m *TranscodeManager) GetStatus() ManagerStatus
- func (m *TranscodeManager) HardwareInfo() *HardwareCapabilities
- func (m *TranscodeManager) Queue() QueueAPI
- func (m *TranscodeManager) Run(ctx context.Context)
- func (m *TranscodeManager) Stop()
- type TranscodeOptions
- type TranscodeQueue
- func (q *TranscodeQueue) ActiveCount() int
- func (q *TranscodeQueue) CancelTask(ctx context.Context, id int64) error
- func (q *TranscodeQueue) Enqueue(ctx context.Context, task *storage.TranscodeTask) error
- func (q *TranscodeQueue) GetStatus() ManagerStatus
- func (q *TranscodeQueue) Run(ctx context.Context) error
- func (q *TranscodeQueue) SetCompletionFunc(fn CompletionFunc)
- func (q *TranscodeQueue) Stop()
- type TranscodeStatus
- type TranscodeTask
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildFFmpegCommand ¶
func BuildFFmpegCommand(opts TranscodeOptions, caps HardwareCapabilities) ([]string, error)
BuildFFmpegCommand constructs the FFmpeg argument array for a transcode job. Returns the complete argument list ready for exec.Command.
func CheckSufficient ¶
func CheckSufficient(caps *HardwareCapabilities, requiredCodec string) (bool, string)
CheckSufficient checks whether the hardware can handle transcoding for the given codec. Returns (true, "") if sufficient, or (false, reason) if not.
func CleanOrphanedTranscodes ¶
func CleanOrphanedTranscodes(ctx context.Context, dataDir string, db DBTaskLister) error
CleanOrphanedTranscodes walks dataDir for files matching *.transcoded.mp4 and deletes any that do not have a corresponding task in the database. This handles crash recovery: orphaned output files left from tasks that were never recorded in DB (e.g., process died mid-enqueue) or whose tasks were cleaned up by DeleteCompletedTasks.
func GetDisabledReason ¶
func GetDisabledReason() string
GetDisabledReason returns the stored disabled reason. Returns empty string if transcoding was never disabled or reason was cleared.
func ResetProbe ¶
func ResetProbe()
ResetProbe clears the cached probe result (for testing). Exported because cross-package tests (e.g. internal/timelapse) need to reset the cache between subtests.
func SetDisabledReason ¶
func SetDisabledReason(reason string)
SetDisabledReason stores the reason why transcoding was disabled. Thread-safe. Called from main.go when NewTranscodeManager fails.
Types ¶
type CompletionFunc ¶ added in v0.6.0
type CompletionFunc func(task *storage.TranscodeTask, success bool)
CompletionFunc is called after a transcode task finishes (success or failure). Implementations handle post-processing like DB registration or file cleanup.
type DBTaskLister ¶
type DBTaskLister interface {
ListTranscodeTasks(ctx context.Context, f storage.TranscodeTaskFilter) ([]storage.TranscodeTask, int, error)
}
DBTaskLister abstracts the database operations needed for orphan cleanup.
type DownloadState ¶
type DownloadState struct {
Status string `json:"status"`
Progress float64 `json:"progress"`
Version string `json:"version"`
Error string `json:"error"`
DownloadURL string `json:"download_url,omitempty"`
LastUpdated string `json:"last_updated"`
TotalBytes int64 `json:"total_bytes,omitempty"`
DownloadedBytes int64 `json:"downloaded_bytes,omitempty"`
}
DownloadState persists download progress across restarts.
type DownloadStatus ¶
type DownloadStatus struct {
Status string `json:"status"` // "not_installed", "downloading", "available", "failed"
Progress float64 `json:"progress"` // 0.0-1.0
Version string `json:"version"`
Error string `json:"error"`
TotalBytes int64 `json:"total_bytes"` // total size of download in bytes
DownloadedBytes int64 `json:"downloaded_bytes"` // bytes downloaded so far
}
DownloadStatus represents the FFmpeg download state for the frontend.
type Downloader ¶
type Downloader struct {
// contains filtered or unexported fields
}
Downloader manages FFmpeg static binary downloads. It is safe for concurrent use — a mutex prevents duplicate downloads.
func NewDownloader ¶
func NewDownloader(dataDir string, onProgress func(downloaded, total int64)) *Downloader
NewDownloader creates a new FFmpeg downloader. dataDir is the storage root (e.g. StorageConfig.RootDir). onProgress is an optional callback invoked with bytes downloaded and total.
func (*Downloader) Cancel ¶
func (d *Downloader) Cancel()
Cancel cancels an active download. Safe to call when nothing is downloading.
func (*Downloader) DownloadFFmpeg ¶
func (d *Downloader) DownloadFFmpeg(ctx context.Context) error
DownloadFFmpeg downloads FFmpeg static binary. Idempotent: if FFmpeg exists and is valid, returns nil immediately. Concurrent-safe: a second call while downloading returns an error.
func (*Downloader) FFmpegPath ¶
func (d *Downloader) FFmpegPath() string
FFmpegPath returns the expected FFmpeg binary path.
func (*Downloader) FFprobePath ¶
func (d *Downloader) FFprobePath() string
FFprobePath returns the expected ffprobe binary path.
func (*Downloader) GetFFmpegStatus ¶
func (d *Downloader) GetFFmpegStatus() DownloadStatus
GetFFmpegStatus checks FFmpeg availability and download status. It checks: 1) system PATH, 2) custom {dataDir}/tools/ffmpeg, 3) download state.
func (*Downloader) StatePath ¶
func (d *Downloader) StatePath() string
StatePath returns the path for download state persistence.
type EncoderType ¶
type EncoderType string
EncoderType represents the encoder backend.
const ( EncoderSoftware EncoderType = "software" EncoderV4L2M2M EncoderType = "v4l2m2m" EncoderVAAPI EncoderType = "vaapi" EncoderNVENC EncoderType = "nvenc" )
type HardwareCapabilities ¶
type HardwareCapabilities struct {
Arch string `json:"arch"`
TotalCores int `json:"total_cores"`
TotalMemoryMB uint64 `json:"total_memory_mb"`
H264Encoder string `json:"h264_encoder"` // encoder name (e.g. "libx264", "h264_v4l2m2m")
H265Encoder string `json:"h265_encoder"` // encoder name (e.g. "libx265", "h265_v4l2m2m")
H264EncoderType EncoderType `json:"h264_encoder_type"` // backend type
H265EncoderType EncoderType `json:"h265_encoder_type"` // backend type
H264Decoder string `json:"h264_decoder"` // decoder name (e.g. "h264_v4l2m2m", "" means software-only)
H265Decoder string `json:"h265_decoder"` // decoder name (e.g. "hevc_v4l2m2m", "" means software-only)
H264DecoderType EncoderType `json:"h264_decoder_type"` // reuse EncoderType enum for decoders
H265DecoderType EncoderType `json:"h265_decoder_type"` // reuse EncoderType enum for decoders
MaxEncodeWidth int `json:"max_encode_width"` // max output width supported by encoder (0 = unlimited)
MaxEncodeHeight int `json:"max_encode_height"` // max output height supported by encoder (0 = unlimited)
Devices []string `json:"devices"` // /dev/video* paths
MaxConcurrentStreams int `json:"max_concurrent_streams"` // estimated safe concurrency
EstimatedFPS float64 `json:"estimated_fps"` // estimated encoding FPS
FFmpegAvailable bool `json:"ffmpeg_available"` // whether FFmpeg is installed
FFmpegPath string `json:"ffmpeg_path"` // path to FFmpeg binary
}
HardwareCapabilities holds the results of a hardware probe.
func ProbeHardwareCapabilities ¶
func ProbeHardwareCapabilities(ffmpegPath string) *HardwareCapabilities
ProbeHardwareCapabilities probes the system for transcoding capabilities. Uses sync.Once for idempotent caching — zero overhead after first call.
func ProbeHardwareCapabilitiesExplicit ¶ added in v0.7.0
func ProbeHardwareCapabilitiesExplicit(ffmpegPath string) *HardwareCapabilities
ProbeHardwareCapabilitiesExplicit probes with a specific FFmpeg path without caching. Used when FFmpeg was downloaded after startup and the cached probe result is stale.
type ManagerConfig ¶
type ManagerConfig struct {
Transcoding config.TranscodingConfig
DataDir string
FFmpegPath string
FFprobePath string
MaxWorkers int
ReplaceOriginal bool
EventBus *event.EventBus
Config *config.Config
}
ManagerConfig holds the dependencies for TranscodeManager.
type ManagerStatus ¶
type ManagerStatus struct {
Enabled bool `json:"enabled"`
DisabledReason string `json:"disabled_reason"`
Hardware *HardwareCapabilities `json:"hardware"`
QueueLength int `json:"queue_length"`
ActiveJobs int `json:"active_jobs"`
RecentResults []TranscodeTask `json:"recent_results"`
}
ManagerStatus is returned by the API status endpoint.
type MediaInfo ¶
type MediaInfo struct {
CodecName string `json:"codec_name"`
Duration float64 `json:"duration"`
Width int `json:"width"`
Height int `json:"height"`
}
MediaInfo holds the result of an ffprobe invocation.
func GetMediaInfo ¶
GetMediaInfo extracts codec, duration, and resolution from a media file.
It prefers the pure-Go mediaprobe path (no external process, reads only MP4 box metadata) for MP4 files, and falls back to ffprobe when mediaprobe fails or the input is not MP4. This removes the hard ffprobe dependency for the common case while keeping ffprobe as a fallback for edge cases (non-MP4 containers, corrupted moov, etc.).
When ffprobePath is empty, ffprobe is looked up on PATH; if it is not available the ffprobe fallback is skipped and only the mediaprobe result is returned (which may be an error for non-MP4 inputs).
type QueueAPI ¶
type QueueAPI interface {
Enqueue(ctx context.Context, task *storage.TranscodeTask) error
CancelTask(ctx context.Context, id int64) error
}
QueueAPI defines the interface for transcode queue operations. Both TranscodeQueue and test mocks implement this interface.
type QueueConfig ¶
type QueueConfig struct {
DataDir string // root data directory for orphan cleanup
MaxWorkers int
FFmpegPath string
FFprobePath string
ReplaceOriginal bool
JobTimeout time.Duration // per-job timeout, 0 means no timeout
}
QueueConfig holds configuration for the transcode queue.
type TranscodeManager ¶
type TranscodeManager struct {
// contains filtered or unexported fields
}
TranscodeManager is the top-level manager for the transcoding subsystem. Follows the MergeManager lifecycle pattern: New → Run(ctx) → Stop. It is nil-safe: all methods handle nil receivers gracefully so callers don't need nil checks (e.g. app.transcodeMgr.Stop() is safe when disabled).
func NewTranscodeManager ¶
func NewTranscodeManager(store *storage.DB, cfg ManagerConfig, m *metrics.Metrics) (*TranscodeManager, error)
NewTranscodeManager probes hardware, validates capabilities, and creates the manager. Returns an error if the self-check fails (FFmpeg missing, no encoder, etc.). The caller should check the error and only assign the manager on success.
func (*TranscodeManager) Downloader ¶
func (m *TranscodeManager) Downloader() *Downloader
Downloader returns the FFmpeg downloader for API access. Returns nil if the manager is nil (transcoding disabled).
func (*TranscodeManager) EnqueueRecording ¶
func (m *TranscodeManager) EnqueueRecording(cameraID, recordingID, inputPath, inputFormat string, targetCodec, bitrate string, crf int) error
EnqueueRecording creates a transcode task for a completed recording segment. The call is non-blocking — the task is inserted into the database queue and will be picked up by a worker goroutine. bitrate and crf come from the per-camera transcoding config (0/empty = use defaults).
func (*TranscodeManager) GetStatus ¶
func (m *TranscodeManager) GetStatus() ManagerStatus
GetStatus returns the current manager and queue status.
func (*TranscodeManager) HardwareInfo ¶
func (m *TranscodeManager) HardwareInfo() *HardwareCapabilities
HardwareInfo returns the probed hardware capabilities. Returns nil if the manager is nil (transcoding disabled).
func (*TranscodeManager) Queue ¶
func (m *TranscodeManager) Queue() QueueAPI
Queue returns the transcode queue for API access (cancel, status, etc.). Returns nil if the manager is nil (transcoding disabled).
func (*TranscodeManager) Run ¶
func (m *TranscodeManager) Run(ctx context.Context)
Run starts the transcoding queue workers and the FFmpeg status watcher. Blocks until ctx is cancelled.
func (*TranscodeManager) Stop ¶
func (m *TranscodeManager) Stop()
Stop cancels the manager context and gracefully drains the queue.
type TranscodeOptions ¶
type TranscodeOptions struct {
InputPath string `json:"input_path"`
OutputPath string `json:"output_path"`
InputCodec string `json:"input_codec"` // "h264", "h265", "mjpeg"
OutputCodec string `json:"output_codec"` // "h264", "h265"
Width int `json:"width"` // output width
Height int `json:"height"` // output height
Bitrate string `json:"bitrate"` // e.g. "2M"
Framerate int `json:"framerate"` // output framerate
ForceSoftware bool `json:"force_software"` // bypass hardware encoders
Preset string `json:"preset"` // "ultrafast", "faster", "medium"
// CRF (Constant Rate Factor) controls quality for libx264/libx265 software encoders.
// Lower = higher quality + larger file. Range 0-51 (libx264 default 23, libx265 28).
// 0 means use the encoder's default (no explicit -crf flag override beyond the default).
CRF int `json:"crf"`
}
TranscodeOptions holds parameters for building an FFmpeg command.
type TranscodeQueue ¶
type TranscodeQueue struct {
// contains filtered or unexported fields
}
TranscodeQueue manages async transcoding tasks with a bounded worker pool. Tasks are dequeued from the database (FIFO) and dispatched to worker goroutines.
func NewTranscodeQueue ¶
func NewTranscodeQueue(store *storage.DB, caps *HardwareCapabilities, dl *Downloader, cfg QueueConfig, m *metrics.Metrics) *TranscodeQueue
NewTranscodeQueue creates a new TranscodeQueue.
func (*TranscodeQueue) ActiveCount ¶
func (q *TranscodeQueue) ActiveCount() int
ActiveCount returns the number of currently running tasks.
func (*TranscodeQueue) CancelTask ¶
func (q *TranscodeQueue) CancelTask(ctx context.Context, id int64) error
CancelTask cancels a running or pending task. For running tasks, it kills the FFmpeg process via the stored cancel function.
func (*TranscodeQueue) Enqueue ¶
func (q *TranscodeQueue) Enqueue(ctx context.Context, task *storage.TranscodeTask) error
Enqueue inserts a new pending task into the database. Rejects tasks that would require software encoding on ARM architecture. Rejects tasks with input codecs that lack hardware decoders on ARM. Rejects tasks where input resolution exceeds encoder limits.
func (*TranscodeQueue) GetStatus ¶
func (q *TranscodeQueue) GetStatus() ManagerStatus
GetStatus returns the current queue status for the API.
func (*TranscodeQueue) Run ¶
func (q *TranscodeQueue) Run(ctx context.Context) error
Run starts the queue's main polling loop. It blocks until ctx is cancelled. On each tick it checks for pending tasks and dispatches up to MaxWorkers concurrently.
func (*TranscodeQueue) SetCompletionFunc ¶ added in v0.6.0
func (q *TranscodeQueue) SetCompletionFunc(fn CompletionFunc)
SetCompletionFunc registers a callback invoked after each task completes. The callback receives the task and whether it succeeded.
func (*TranscodeQueue) Stop ¶
func (q *TranscodeQueue) Stop()
Stop gracefully drains the queue. Active workers are allowed to finish. New tasks will not be dequeued after this call.
type TranscodeStatus ¶
type TranscodeStatus string
TranscodeStatus represents the status of a transcoding task.
const ( StatusPending TranscodeStatus = "pending" StatusRunning TranscodeStatus = "running" StatusCompleted TranscodeStatus = "completed" StatusFailed TranscodeStatus = "failed" StatusCancelled TranscodeStatus = "cancelled" )
type TranscodeTask ¶
type TranscodeTask struct {
ID int64 `json:"id"`
CameraID string `json:"camera_id"`
RecordingID string `json:"recording_id"`
InputPath string `json:"input_path"`
InputFormat string `json:"input_format"`
OutputPath string `json:"output_path"`
OutputFormat string `json:"output_format"`
Status TranscodeStatus `json:"status"`
Progress float64 `json:"progress"`
Error string `json:"error"`
CreatedAt string `json:"created_at"`
StartedAt *string `json:"started_at"`
CompletedAt *string `json:"completed_at"`
OriginalDeleted bool `json:"original_deleted"`
}
TranscodeTask represents a transcoding task stored in the database.