347 lines
8.5 KiB
C#
347 lines
8.5 KiB
C#
/* Copyright (c) 2019-present Evereal. All rights reserved. */
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using UnityEngine;
|
|
|
|
namespace Evereal.VideoCapture
|
|
{
|
|
// This script will merge video/audio to flv and publish to remote streaming service.
|
|
public class FFmpegStreamer : MonoBehaviour
|
|
{
|
|
#region Dll Import
|
|
|
|
[DllImport("FFmpegEncoder")]
|
|
private static extern IntPtr FFmpegEncoder_StartMuxingProcess(
|
|
EncoderPreset preset,
|
|
int bitrate,
|
|
string mediaPath,
|
|
string videoPath,
|
|
string audioPath,
|
|
string ffmpegPath,
|
|
bool isLive);
|
|
|
|
[DllImport("FFmpegEncoder")]
|
|
private static extern void FFmpegEncoder_CleanMuxingProcess(IntPtr api);
|
|
|
|
[DllImport("FFmpegEncoder")]
|
|
private static extern IntPtr FFmpegEncoder_StartLiveStreaming(
|
|
string streamUrl,
|
|
string ffmpegPath);
|
|
|
|
[DllImport("FFmpegEncoder")]
|
|
private static extern bool FFmpegEncoder_SendLiveVideo(IntPtr api, string file);
|
|
|
|
[DllImport("FFmpegEncoder")]
|
|
private static extern bool FFmpegEncoder_StopLiveStreaming(IntPtr api);
|
|
|
|
[DllImport("FFmpegEncoder")]
|
|
private static extern void FFmpegEncoder_CleanLiveStreaming(IntPtr api);
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public static FFmpegStreamer singleton;
|
|
|
|
// Live stream url
|
|
public string liveStreamUrl = "";
|
|
// Video bitrate
|
|
public Int32 bitrate = 2000;
|
|
|
|
// Live stream is already started
|
|
public bool streamStarted { get; private set; }
|
|
// If live stream with audio
|
|
public bool captureAudio { get; set; }
|
|
|
|
// Video slice for live streaming.
|
|
private Queue<string> videoSliceQueue;
|
|
// Audio slice for live streaming.
|
|
private Queue<string> audioSliceQueue;
|
|
// Flv live streaming video slice.
|
|
private Queue<string> liveSliceQueue;
|
|
// Pending delete file queue
|
|
private Queue<string> deleteSliceQueue;
|
|
|
|
// The audio/video mux thread.
|
|
private Thread muxingThread;
|
|
|
|
// The live streaming thread.
|
|
private Thread streamThread;
|
|
|
|
AutoResetEvent muxReady = new AutoResetEvent(false);
|
|
|
|
// The ffmpeg executable path.
|
|
public string ffmpegFullPath { get; set; }
|
|
|
|
// Reference to native encoder API
|
|
private IntPtr nativeAPI;
|
|
|
|
// Log message format template
|
|
private string LOG_FORMAT = "[FFmpegStreamer] {0}";
|
|
|
|
#endregion
|
|
|
|
#region Live Streaming
|
|
|
|
public bool StartStream()
|
|
{
|
|
// Check if we can start stream session
|
|
if (streamStarted)
|
|
return false;
|
|
|
|
// Check if live stream url is correct set
|
|
if (string.IsNullOrEmpty(liveStreamUrl))
|
|
return false;
|
|
|
|
// Reset slice queue
|
|
videoSliceQueue = new Queue<string>();
|
|
audioSliceQueue = new Queue<string>();
|
|
liveSliceQueue = new Queue<string>();
|
|
deleteSliceQueue = new Queue<string>();
|
|
|
|
if (streamThread != null && streamThread.IsAlive)
|
|
{
|
|
streamThread.Abort();
|
|
streamThread = null;
|
|
}
|
|
|
|
streamStarted = true;
|
|
|
|
nativeAPI = FFmpegEncoder_StartLiveStreaming(
|
|
liveStreamUrl,
|
|
ffmpegFullPath);
|
|
|
|
if (nativeAPI == IntPtr.Zero)
|
|
{
|
|
Debug.LogErrorFormat(LOG_FORMAT, "Start live streaming failed!");
|
|
return false;
|
|
}
|
|
|
|
if (captureAudio)
|
|
{
|
|
// Start video/audio thread.
|
|
if (muxingThread != null)
|
|
{
|
|
if (muxingThread.IsAlive)
|
|
muxingThread.Abort();
|
|
muxingThread = null;
|
|
}
|
|
muxingThread = new Thread(MuxingThreadFunction);
|
|
muxingThread.Priority = System.Threading.ThreadPriority.Normal;
|
|
//muxingThread.IsBackground = true;
|
|
muxingThread.Start();
|
|
}
|
|
|
|
// Start live stream thread.
|
|
if (streamThread != null)
|
|
{
|
|
if (streamThread.IsAlive)
|
|
streamThread.Abort();
|
|
streamThread = null;
|
|
}
|
|
streamThread = new Thread(LiveStreamThreadProcess);
|
|
streamThread.Priority = System.Threading.ThreadPriority.Normal;
|
|
//streamThread.IsBackground = true;
|
|
streamThread.Start();
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool StopStream()
|
|
{
|
|
if (!streamStarted)
|
|
return false;
|
|
|
|
streamStarted = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public void EnqueueVideoSlice(string slice)
|
|
{
|
|
if (captureAudio)
|
|
{
|
|
lock (videoSliceQueue)
|
|
{
|
|
videoSliceQueue.Enqueue(slice);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lock (liveSliceQueue)
|
|
{
|
|
liveSliceQueue.Enqueue(slice);
|
|
muxReady.Set();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void EnqueueAudioSlice(string slice)
|
|
{
|
|
lock (audioSliceQueue)
|
|
{
|
|
audioSliceQueue.Enqueue(slice);
|
|
}
|
|
}
|
|
|
|
// Muxing video/audio in thread
|
|
private void MuxingThreadFunction()
|
|
{
|
|
while (streamStarted || (videoSliceQueue.Count > 0 && audioSliceQueue.Count > 0))
|
|
{
|
|
if (videoSliceQueue.Count > 0 && audioSliceQueue.Count > 0)
|
|
{
|
|
string videoSlice;
|
|
string audioSlice;
|
|
lock (videoSliceQueue)
|
|
{
|
|
videoSlice = videoSliceQueue.Dequeue();
|
|
}
|
|
lock (audioSliceQueue)
|
|
{
|
|
audioSlice = audioSliceQueue.Dequeue();
|
|
}
|
|
string ext = Path.GetExtension(videoSlice);
|
|
string liveSlice = videoSlice.Replace(ext, ".flv");
|
|
|
|
int waitCount = 0;
|
|
while (!File.Exists(liveSlice) && waitCount++ < 10)
|
|
{
|
|
Thread.Sleep(1000);
|
|
IntPtr muxingAPI = FFmpegEncoder_StartMuxingProcess(
|
|
EncoderPreset.H264_MP4,
|
|
bitrate,
|
|
liveSlice,
|
|
videoSlice,
|
|
audioSlice,
|
|
ffmpegFullPath,
|
|
true);
|
|
if (muxingAPI == IntPtr.Zero)
|
|
return;
|
|
FFmpegEncoder_CleanMuxingProcess(muxingAPI);
|
|
}
|
|
|
|
if (waitCount >= 10)
|
|
return;
|
|
|
|
if (streamStarted)
|
|
{
|
|
lock (liveSliceQueue)
|
|
{
|
|
liveSliceQueue.Enqueue(liveSlice);
|
|
muxReady.Set();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (File.Exists(liveSlice))
|
|
File.Delete(liveSlice);
|
|
}
|
|
|
|
// Clean temp file
|
|
if (File.Exists(videoSlice))
|
|
File.Delete(videoSlice);
|
|
if (File.Exists(audioSlice))
|
|
File.Delete(audioSlice);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Live streaming process in thread
|
|
private void LiveStreamThreadProcess()
|
|
{
|
|
while (streamStarted || liveSliceQueue.Count > 0)
|
|
{
|
|
if (liveSliceQueue.Count > 0)
|
|
{
|
|
string liveSlice;
|
|
lock (liveSliceQueue)
|
|
{
|
|
liveSlice = liveSliceQueue.Dequeue();
|
|
}
|
|
if (!FFmpegEncoder_SendLiveVideo(nativeAPI, liveSlice))
|
|
{
|
|
Debug.LogWarningFormat(LOG_FORMAT, "Failed to send live stream video file!");
|
|
}
|
|
|
|
while (deleteSliceQueue.Count > 0)
|
|
{
|
|
string deleteSlice = deleteSliceQueue.Dequeue();
|
|
// Clean live video file
|
|
if (File.Exists(deleteSlice))
|
|
File.Delete(deleteSlice);
|
|
}
|
|
|
|
deleteSliceQueue.Enqueue(liveSlice);
|
|
}
|
|
else if (streamStarted)
|
|
{
|
|
// Wait new captured frames
|
|
muxReady.WaitOne();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
FFmpegEncoder_StopLiveStreaming(nativeAPI);
|
|
FFmpegEncoder_CleanLiveStreaming(nativeAPI);
|
|
|
|
// Clean all temp file
|
|
while (videoSliceQueue.Count > 0)
|
|
{
|
|
string videoSlice;
|
|
lock (videoSliceQueue)
|
|
{
|
|
videoSlice = videoSliceQueue.Dequeue();
|
|
}
|
|
if (File.Exists(videoSlice))
|
|
File.Delete(videoSlice);
|
|
}
|
|
while (audioSliceQueue.Count > 0)
|
|
{
|
|
string audioSlice;
|
|
lock (audioSliceQueue)
|
|
{
|
|
audioSlice = audioSliceQueue.Dequeue();
|
|
}
|
|
if (File.Exists(audioSlice))
|
|
File.Delete(audioSlice);
|
|
}
|
|
while (deleteSliceQueue.Count > 0)
|
|
{
|
|
string deleteSlice;
|
|
lock (deleteSliceQueue)
|
|
{
|
|
deleteSlice = deleteSliceQueue.Dequeue();
|
|
}
|
|
if (File.Exists(deleteSlice))
|
|
File.Delete(deleteSlice);
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (singleton != null)
|
|
return;
|
|
singleton = this;
|
|
|
|
streamStarted = false;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (streamStarted)
|
|
{
|
|
StopStream();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |