Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/OpenIPC.Viewer.Video/Recording/FfmpegRecordingSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
19 changes: 19 additions & 0 deletions src/OpenIPC.Viewer.Video/Recording/LibavformatRecordingSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -171,6 +172,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");
Expand All @@ -183,8 +196,14 @@ private unsafe void Run()
outStream->codecpar->codec_tag = 0;

streamMap[i] = (int)outStream->index;
hasVideo |= kind == AVMediaType.AVMEDIA_TYPE_VIDEO;
}

// 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)
{
ret = ffmpeg.avio_open(&outputCtx->pb, _outputPath, ffmpeg.AVIO_FLAG_WRITE);
Expand Down
Loading