// Return a no-longer-used frame to the pool private void ReturnFrameToPool(Bytable array) { lock (m_videoFramePool) { m_videoFramePool.Enqueue((Color32[])array.GetArray()); } }
private void WriteFramesToFfmpeg() { Bytable curFrame = null; byte[] buf = null; System.IO.BinaryWriter dataPipe = m_dataWriter; try { while (!m_shouldExit) { while (!m_shouldPause) { // Wait for the next frame to arrive. m_frameReady.WaitOne(); m_frameWritten.Reset(); // Grab the frame. curFrame = Interlocked.Exchange(ref m_frame, curFrame); if (curFrame == null || curFrame.Length == 0) { // This really shouldn't happen. UnityEngine.Debug.LogWarning("FfmpegPipe recieved invalid frame"); m_frameWritten.Set(); continue; } curFrame.ToBytes(ref buf); dataPipe.Write(buf); if (ReleaseFrame != null) { ReleaseFrame(curFrame); } m_frameCount++; m_frameWritten.Set(); // It's safe to throw this memory away, because Unity is transferring ownership. curFrame = null; } // Wait for the next frame m_ready.WaitOne(); } dataPipe.Flush(); dataPipe.Close(); } catch (System.Threading.ThreadInterruptedException) { // This is fine, the render thread sent an interrupt. dataPipe.Flush(); dataPipe.Close(); } catch (System.Exception e) { UnityEngine.Debug.LogWarning(m_outputFile); UnityEngine.Debug.LogException(e); } }
// Returns false if ffmpeg fails to start. // Note that sampleRate is FPS for video and sample rate in Hz for audio public bool Start(string source, string outputFile, int width, int height, float sampleRate, bool blocking) { m_outputFile = ""; if (!LaunchEncoder(source, outputFile, width, height, sampleRate)) { // ffmpeg failed to launch return(false); } m_outputFile = outputFile; m_height = height; m_width = width; m_shouldBlock = blocking; m_frameCount = 0; m_shouldExit = false; m_shouldPause = true; m_frame = null; m_stderr = m_encoderProc.StandardError; // Start status reader m_logReaderThread = new Thread(ReadFfmpegOutput); m_logReaderThread.IsBackground = true; m_logReaderThread.Start(); if (source.StartsWith("pipe:")) { m_dataWriter = new BinaryWriter(m_encoderProc.StandardInput.BaseStream); m_dataReader = null; // Start frame writer m_dataWriterThread = new Thread(WriteFramesToFfmpeg); m_dataWriterThread.IsBackground = true; m_dataWriterThread.Start(); } else { m_dataWriter = null; m_dataReader = new BinaryReader(m_encoderProc.StandardOutput.BaseStream); m_dataReaderThread = new Thread(ReadFramesFromFfmpeg); m_dataReaderThread.IsBackground = true; m_dataReaderThread.Start(); m_framesOut = new RingBuffer <Bytable>(5); } m_shouldPause = false; m_ready.Set(); return(true); }
/// Queues a buffer to the encoder and returns the previously queued buffer for reuse. public Bytable QueueFrame(Bytable buffer) { // Hand off to writer thread. // We may drop frames here if we don't write them fast enough, unless we have chosen to block. // Explanation of the blocking behavior follows: // What happens here is that m_frameWritten will not block the first time through. While the // frame is being encoded, m_frameWritten is reset, so that if we get here again before the // frame is done with, it will block until the frame has finished. In this way it will not // queue a frame when one is encoding, but once it has queued one, it will return to do // other things while that goes on in the background. if (m_shouldBlock) { // Assumption here is that it shouldn't take longer than five seconds to encode a single // frame. If it takes longer we'll just start using up the buffer. m_frameWritten.WaitOne(5 * 1000); } Interlocked.Exchange(ref m_frame, buffer); m_frameReady.Set(); return buffer; }
private void ReadFramesFromFfmpeg() { long PIXEL_SIZE_BYTES = System.Runtime.InteropServices.Marshal.SizeOf(typeof(Color32)); // TODO: Use ffprobe instead of width/height, then w/h properties could be removed. byte[] buf = new byte[Height * Width * PIXEL_SIZE_BYTES]; System.IO.BinaryReader dataPipe = m_dataReader; // Keep a local set of buffers to avoid garbage collection. Bytable[] localRefs = new Bytable[m_framesOut.Capacity]; // Init local refs. int lastLocalRef = 0; for (int i = 0; i < localRefs.Length; i++) { localRefs[i] = new Color32Bytable(null); } try { using (dataPipe) { while (!m_shouldExit) { while (!m_shouldPause) { if (m_framesOut.IsFull) { // Wait for the consumer. m_frameReady.WaitOne(); } int bytesRead = dataPipe.Read(buf, 0, buf.Length); while (bytesRead < buf.Length) { bytesRead += dataPipe.Read(buf, bytesRead, buf.Length - bytesRead); if (bytesRead == 0) { return; } } if (bytesRead != buf.Length) { // For some reason we only read the wrong amount of data. UnityEngine.Debug.LogWarningFormat("BAD READ RESULT: got {0} bytes, expected {1}", bytesRead, buf.Length); continue; } // If the last buffer we had was the same size, no allocation will happen here. We // will also be holding a reference to that array, so even after it's removed from the // m_framesOut buffer, it should not generate garbage. Bytable curFrame = localRefs[lastLocalRef]; lastLocalRef = (lastLocalRef + 1) % localRefs.Length; curFrame.FromBytes(buf); // If called with overwriteIfFull=true, this code will require a lock. m_framesOut.Enqueue(curFrame); m_frameCount++; } // Wait for the next frame m_ready.WaitOne(); } } } catch (System.Threading.ThreadInterruptedException) { // This is fine, the render thread sent an interrupt. } catch (System.Exception e) { UnityEngine.Debug.LogException(e); } finally { Stop(); } }