/// <summary> /// Initialise an encoder job based on previously setup capture devices. /// Need to have one job per 'ReductionFactor' in the config. /// </summary> private void EncoderSetup() { var factors = config.EncoderSettings.ReductionFactors; Packages = new List <EncoderPackage>(); int fps = (cam != null) ? (cam.m_frameRate) : (config.Video.InputFrameRate); var needed_packages = ListRequiredPackages(); int pkg_id = 0; foreach (var np in needed_packages) { EncoderJob job = new EncoderJob(); job.OldSegmentNumber = 1; string joined = Path.Combine(config.EncoderSettings.LocalSystemOutputFolder, config.EncoderSettings.LocalSystemFilePrefix); joined += "_" + pkg_id; int bitrate = (int)(config.EncoderSettings.VideoBitrate * np.Quality); // linear scale int error = EncoderBridge.InitialiseEncoderJob( ref job, // job to complete np.VideoSize.Width, // OUTPUT video width np.VideoSize.Height, // OUTPUT video height joined, // OUTPUT folder + filename prefix fps, // INPUT frame rate (output will match) bitrate, // OUTPUT video bit rate config.EncoderSettings.FragmentSeconds); // Fragment length (seconds) if (error != 0) { throw new Exception("Encoder setup error #" + error); } if (!job.IsValid) { throw new Exception("Job rejected"); } var mf = new MediaFrame(); mf.ForceAudioConsumption = (np.HasVideo) ? ((byte)0) : ((byte)1); // don't sync if no video. var pkg = new EncoderPackage(np, pkg_id, job, mf); ConnectPackageToBuffers(pkg, np); Packages.Add(pkg); pkg_id++; } }
/// <summary> /// Create, start and return a new core encoder thread. /// </summary> private Thread NewEncoderThread(EncoderPackage pkg) { ParameterizedThreadStart encodeLoop = new ParameterizedThreadStart(EncoderCoreLoop); var encloop = new Thread(encodeLoop); encloop.IsBackground = true; // When the parent thread ends, the encoder thread will be aborted. encloop.SetApartmentState(ApartmentState.MTA); // Don't use 'Highest' priority, as that can cause issues with the capture drivers (which will be working hard too) encloop.Priority = ThreadPriority.AboveNormal; encloop.Start(pkg); // start with the job id return(encloop); }
/// <summary> /// Create encoder buffers for each package, and add to capture buffer lists. /// </summary> private void ConnectPackageToBuffers(EncoderPackage Package, PackageSpec Spec) { if (Spec.HasVideo && ImageBuffers != null) { var vbuf = new ImageBuffer(Spec.VideoSize.Width, Spec.VideoSize.Height); ImageBuffers.Add(vbuf); Package.Buffers.Add(vbuf); } if (Spec.HasAudio && AudioBuffers != null) { AudioBufferMono abuf = null; if (mic != null) { abuf = new AudioBufferMono(mic.SampleRate, mic.Channels); } else { abuf = new AudioBufferMono(config.Audio.SampleRate, config.Audio.Channels); } AudioBuffers.Add(abuf); Package.Buffers.Add(abuf); } }
/// <summary> /// This loop does the actual work of reading caches into the encoder and firing actions. /// This loop controls Multiple-bit-rate encoding /// </summary> private void EncoderCoreLoop(object Package) { EncoderPackage pkg = Package as EncoderPackage; try { #region Start up if (pkg == null) { throw new Exception("Encoder core loop package was lost"); } double lastVideoTime = 0.0; // used for Adaptive frame rate if (cam != null) { lastVideoTime = cam.TimecodeStart; } int loop_frame_incr = 0; if (pkg.Specification.HasVideo) { loop_frame_incr = 1; videoJobs++; } #endregion while (!EncoderRunning) // wait for the signal! { System.Threading.Thread.Sleep(1000); } WaitForSyncFlag(); start = DateTime.Now; while (EncoderRunning) // Encode frames until stopped { #region Frame availability checks // Wait for buffers to be populated while (pkg.BuffersEmpty(MinimumBufferPopulation) && EncoderRunning) { foreach (var buf in pkg.Buffers) { buf.RebufferCapturedFrames(); } if (pkg.BuffersEmpty(MinimumBufferPopulation)) { System.Threading.Thread.Sleep(frameSleep); } } if (DryRun) { System.Threading.Thread.Sleep(frameSleep); continue; // don't encode } #endregion pkg.LoadAllBuffers(); EncoderBridge.EncodeFrame(ref pkg.Job, ref pkg.Frame); pkg.UnloadAllBuffers(); if (!pkg.Job.IsValid) { throw new Exception("Job became invalid. Possible memory or filesystem error"); } #region Segment switching if (pkg.Job.SegmentNumber != pkg.Job.OldSegmentNumber) { double real_chunk_duration = pkg.Frame.VideoSampleTime - lastVideoTime; lock (outputRouter) { outputRouter.NewChunkAvailable(pkg.Job.OldSegmentNumber, pkg.JobIndex, real_chunk_duration); } lastVideoTime = pkg.Frame.VideoSampleTime; pkg.Job.OldSegmentNumber = pkg.Job.SegmentNumber; } FrameCount += loop_frame_incr; #endregion } } catch (Exception ex) { System.Diagnostics.Debug.Fail("EncoderController.cs: Core loop fault.", ex.Message + "\r\n" + ex.StackTrace); File.WriteAllText(config.EncoderSettings.LocalSystemOutputFolder + @"/error.txt", "Main loop: " + ex.Message + "\r\n" + ex.StackTrace); } finally { if (pkg != null) { EncoderBridge.CloseEncoderJob(ref pkg.Job); // NEVER FORGET THIS!! } if (EncoderRunning) { Halt(); // Don't use 'Stop()' in the core loop, or the system will freeze! } System.Threading.Thread.CurrentThread.Abort(); } }
/// <summary> /// Create, start and return a new core encoder thread. /// </summary> private Thread NewEncoderThread(EncoderPackage pkg) { ParameterizedThreadStart encodeLoop = new ParameterizedThreadStart(EncoderCoreLoop); var encloop = new Thread(encodeLoop); encloop.IsBackground = true; // When the parent thread ends, the encoder thread will be aborted. encloop.SetApartmentState(ApartmentState.MTA); // Don't use 'Highest' priority, as that can cause issues with the capture drivers (which will be working hard too) encloop.Priority = ThreadPriority.AboveNormal; encloop.Start(pkg); // start with the job id return encloop; }
/// <summary> /// Initialise an encoder job based on previously setup capture devices. /// Need to have one job per 'ReductionFactor' in the config. /// </summary> private void EncoderSetup() { var factors = config.EncoderSettings.ReductionFactors; Packages = new List<EncoderPackage>(); int fps = (cam != null) ? (cam.m_frameRate) : (config.Video.InputFrameRate); var needed_packages = ListRequiredPackages(); int pkg_id = 0; foreach (var np in needed_packages) { EncoderJob job = new EncoderJob(); job.OldSegmentNumber = 1; string joined = Path.Combine(config.EncoderSettings.LocalSystemOutputFolder, config.EncoderSettings.LocalSystemFilePrefix); joined += "_" + pkg_id; int bitrate = (int)(config.EncoderSettings.VideoBitrate * np.Quality); // linear scale int error = EncoderBridge.InitialiseEncoderJob( ref job, // job to complete np.VideoSize.Width, // OUTPUT video width np.VideoSize.Height, // OUTPUT video height joined, // OUTPUT folder + filename prefix fps, // INPUT frame rate (output will match) bitrate, // OUTPUT video bit rate config.EncoderSettings.FragmentSeconds); // Fragment length (seconds) if (error != 0) throw new Exception("Encoder setup error #" + error); if (!job.IsValid) throw new Exception("Job rejected"); var mf = new MediaFrame(); mf.ForceAudioConsumption = (np.HasVideo) ? ((byte)0) : ((byte)1); // don't sync if no video. var pkg = new EncoderPackage(np, pkg_id, job, mf); ConnectPackageToBuffers(pkg, np); Packages.Add(pkg); pkg_id++; } }
/// <summary> /// Create encoder buffers for each package, and add to capture buffer lists. /// </summary> private void ConnectPackageToBuffers(EncoderPackage Package, PackageSpec Spec) { if (Spec.HasVideo && ImageBuffers != null) { var vbuf = new ImageBuffer(Spec.VideoSize.Width, Spec.VideoSize.Height); ImageBuffers.Add(vbuf); Package.Buffers.Add(vbuf); } if (Spec.HasAudio && AudioBuffers != null) { AudioBufferMono abuf = null; if (mic != null) abuf = new AudioBufferMono(mic.SampleRate, mic.Channels); else abuf = new AudioBufferMono(config.Audio.SampleRate, config.Audio.Channels); AudioBuffers.Add(abuf); Package.Buffers.Add(abuf); } }