From fd6bfaeeb894a99d6d1329e3bd32fbc2f7f8e812 Mon Sep 17 00:00:00 2001 From: keyldev Date: Thu, 17 Sep 2026 15:35:17 +0300 Subject: [PATCH 1/3] fix(recording): skip streams mp4 cannot hold instead of failing - G.711 audio (pcm_alaw/pcm_mulaw) from OpenIPC cameras made avformat_write_header fail with EINVAL, so Android recorded nothing. - Streams avformat_query_codec rejects are dropped with a warning. --- .../Recording/LibavformatRecordingSession.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs b/src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs index dc8eb29..dd5ad6a 100644 --- a/src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs +++ b/src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs @@ -171,6 +171,18 @@ private unsafe void Run() if (kind != AVMediaType.AVMEDIA_TYPE_VIDEO && kind != AVMediaType.AVMEDIA_TYPE_AUDIO) continue; + // OpenIPC cameras commonly send G.711 (pcm_alaw/pcm_mulaw) audio, which + // mp4 has no tag for: copying it made avformat_write_header fail with + // EINVAL and no recording at all. Drop what the container can't hold; + // the video is what a recording is for. + var codecId = inStream->codecpar->codec_id; + if (ffmpeg.avformat_query_codec(outputCtx->oformat, codecId, ffmpeg.FF_COMPLIANCE_NORMAL) != 1) + { + _logger.LogWarning("Recording skips stream {Index}: mp4 cannot hold {Codec}", + i, ffmpeg.avcodec_get_name(codecId)); + continue; + } + var outStream = ffmpeg.avformat_new_stream(outputCtx, null); if (outStream == null) throw new InvalidOperationException("avformat_new_stream returned null"); @@ -185,6 +197,9 @@ private unsafe void Run() streamMap[i] = (int)outStream->index; } + if (outputCtx->nb_streams == 0) + throw new InvalidOperationException("The camera stream has no video mp4 can hold"); + if ((outputCtx->oformat->flags & ffmpeg.AVFMT_NOFILE) == 0) { ret = ffmpeg.avio_open(&outputCtx->pb, _outputPath, ffmpeg.AVIO_FLAG_WRITE); From 5b95f4688cf87e5b8e9b1de9a1c0af740f810891 Mon Sep 17 00:00:00 2001 From: keyldev Date: Thu, 17 Sep 2026 15:39:18 +0300 Subject: [PATCH 2/3] fix(recording): desktop re-encodes camera audio to AAC - "-c copy" into mp4 failed on G.711 audio (pcm_alaw/pcm_mulaw), so desktop recorded nothing from such cameras. - Map only video + optional audio, copy video, encode audio to AAC. --- .../Recording/FfmpegRecordingSession.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs b/src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs index c2e8b7e..7db3897 100644 --- a/src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs +++ b/src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs @@ -94,7 +94,15 @@ public void Start() // no row reaches the Recordings tab while REC keeps "running" (#91). psi.ArgumentList.Add(FfmpegCliOptions.RtspTimeoutFlag(_ffmpegPath)); psi.ArgumentList.Add(RtspTimeoutMicros); psi.ArgumentList.Add("-i"); psi.ArgumentList.Add(BuildRtspUri(_options.RtspUri, _options.Credentials).ToString()); - psi.ArgumentList.Add("-c"); psi.ArgumentList.Add("copy"); + // Video is copied untouched. Audio is re-encoded to AAC: OpenIPC cameras + // commonly send G.711 (pcm_alaw/pcm_mulaw), which mp4 has no tag for, and + // a plain "-c copy" failed the header write so nothing was recorded. AAC at + // 8-16 kHz costs next to no CPU. "0:a?" keeps silent cameras working, and + // mapping only video+audio drops ONVIF metadata/data tracks mp4 rejects too. + psi.ArgumentList.Add("-map"); psi.ArgumentList.Add("0:v"); + psi.ArgumentList.Add("-map"); psi.ArgumentList.Add("0:a?"); + psi.ArgumentList.Add("-c:v"); psi.ArgumentList.Add("copy"); + psi.ArgumentList.Add("-c:a"); psi.ArgumentList.Add("aac"); psi.ArgumentList.Add("-f"); psi.ArgumentList.Add("segment"); psi.ArgumentList.Add("-segment_time"); psi.ArgumentList.Add(((int)_options.SegmentDuration.TotalSeconds).ToString(System.Globalization.CultureInfo.InvariantCulture)); psi.ArgumentList.Add("-reset_timestamps"); psi.ArgumentList.Add("1"); From 8f75b298a2bea81def64659d07ee4d898a3622b9 Mon Sep 17 00:00:00 2001 From: keyldev Date: Wed, 11 Nov 2026 18:44:31 +0300 Subject: [PATCH 3/3] fix(recording): require a video stream after codec filtering - If mp4 rejected the video but kept the audio, the recording was audio-only. - Fail before opening the output unless at least one video stream survives. --- .../Recording/LibavformatRecordingSession.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs b/src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs index dd5ad6a..58e2ffe 100644 --- a/src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs +++ b/src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs @@ -163,6 +163,7 @@ private unsafe void Run() outputCtx = outRaw; streamMap = new int[(int)inputCtx->nb_streams]; + var hasVideo = false; for (var i = 0; i < (int)inputCtx->nb_streams; i++) { streamMap[i] = -1; @@ -195,9 +196,12 @@ private unsafe void Run() outStream->codecpar->codec_tag = 0; streamMap[i] = (int)outStream->index; + hasVideo |= kind == AVMediaType.AVMEDIA_TYPE_VIDEO; } - if (outputCtx->nb_streams == 0) + // Audio is optional, video is not: an audio-only file isn't a recording. + // Fail before avio_open so no empty/useless file is left on disk. + if (!hasVideo) throw new InvalidOperationException("The camera stream has no video mp4 can hold"); if ((outputCtx->oformat->flags & ffmpeg.AVFMT_NOFILE) == 0)