优化录像帧率,最大帧率提升到60帧
parent
9a5da24534
commit
6e555569c0
|
@ -1,4 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -17,6 +20,7 @@ namespace ZC
|
||||||
public class RTMPPublisher : MonoBehaviour
|
public class RTMPPublisher : MonoBehaviour
|
||||||
{
|
{
|
||||||
Thread _thread;
|
Thread _thread;
|
||||||
|
private Timer _timer;
|
||||||
Process _process;
|
Process _process;
|
||||||
private RectInt _rectInt;
|
private RectInt _rectInt;
|
||||||
private RTMPConfig _config;
|
private RTMPConfig _config;
|
||||||
|
@ -30,12 +34,16 @@ namespace ZC
|
||||||
|
|
||||||
private volatile int _sendData;
|
private volatile int _sendData;
|
||||||
|
|
||||||
private NativeArray<byte> _textureData;
|
|
||||||
private AsyncGPUReadbackRequest _asyncGPUReadbackRequest;
|
private AsyncGPUReadbackRequest _asyncGPUReadbackRequest;
|
||||||
private bool _isRequesting;
|
private bool _isRequesting;
|
||||||
|
|
||||||
|
|
||||||
private byte[] _bitmapArray;
|
private byte[] _bitmapArray;
|
||||||
|
private ConcurrentQueue<NativeArray<byte>> _textureDataPool;
|
||||||
|
private Queue<(AsyncGPUReadbackRequest, NativeArray<byte>)> _textureDatas;
|
||||||
|
private ConcurrentQueue<(AsyncGPUReadbackRequest, NativeArray<byte>)> _doneTextureDatas;
|
||||||
|
private int _targetTextureWidth;
|
||||||
|
private int _targetTextureHeight;
|
||||||
|
|
||||||
public void SetCamera(Camera camera)
|
public void SetCamera(Camera camera)
|
||||||
{
|
{
|
||||||
|
@ -52,10 +60,40 @@ namespace ZC
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_textureDataPool = new ConcurrentQueue<NativeArray<byte>>();
|
||||||
|
_textureDatas = new Queue<(AsyncGPUReadbackRequest, NativeArray<byte>)>();
|
||||||
|
_doneTextureDatas = new ConcurrentQueue<(AsyncGPUReadbackRequest, NativeArray<byte>)>();
|
||||||
_config.Load();
|
_config.Load();
|
||||||
|
_timer = new Timer(Tick, null, TimeSpan.FromMilliseconds(0), TimeSpan.FromMilliseconds(16));
|
||||||
Setup(Camera.main);
|
Setup(Camera.main);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 其他线程tick
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state"></param>
|
||||||
|
private void Tick(object state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this._doneTextureDatas.TryDequeue(out var result))
|
||||||
|
{
|
||||||
|
var textureData = result.Item2;
|
||||||
|
Profiler.BeginSample("Conversion");
|
||||||
|
Bitmap.EncodeToBitmap(textureData, _bitmapArray, 0, textureData.Length, _targetTextureWidth, this._targetTextureHeight);
|
||||||
|
Profiler.EndSample();
|
||||||
|
Profiler.BeginSample("WriteBuffer");
|
||||||
|
_process.StandardInput.BaseStream.Write(_bitmapArray, 0, _bitmapArray.Length);
|
||||||
|
Profiler.EndSample();
|
||||||
|
_textureDataPool.Enqueue(result.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateCaptureThread()
|
private void CreateCaptureThread()
|
||||||
{
|
{
|
||||||
this._thread = new Thread(this.CreateThread);
|
this._thread = new Thread(this.CreateThread);
|
||||||
|
@ -72,37 +110,59 @@ namespace ZC
|
||||||
{
|
{
|
||||||
if (_sendData != 0)
|
if (_sendData != 0)
|
||||||
{
|
{
|
||||||
if (_camera)
|
if (_camera && !_isRequesting)
|
||||||
{
|
|
||||||
if (!_isRequesting || _asyncGPUReadbackRequest.done)
|
|
||||||
{
|
{
|
||||||
var cameraActiveTexture = _camera.targetTexture;
|
var cameraActiveTexture = _camera.targetTexture;
|
||||||
RenderTexture.active = cameraActiveTexture;
|
RenderTexture.active = cameraActiveTexture;
|
||||||
_asyncGPUReadbackRequest = AsyncGPUReadback.RequestIntoNativeArray(ref _textureData, cameraActiveTexture, 0,
|
if (!_textureDataPool.TryDequeue(out var nativeArray))
|
||||||
GraphicsFormat.B8G8R8_SRGB, ReadTextureCallback);
|
{
|
||||||
_isRequesting = true;
|
var targetTextureByteCount = this._camera.targetTexture.width * _camera.targetTexture.height * 3;
|
||||||
}
|
nativeArray = new NativeArray<byte>(targetTextureByteCount, Allocator.Persistent);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadTextureCallback(AsyncGPUReadbackRequest obj)
|
var asyncGPUReadbackRequest = AsyncGPUReadback.RequestIntoNativeArray(ref nativeArray, cameraActiveTexture, 0, GraphicsFormat.B8G8R8_SRGB, null);
|
||||||
|
_textureDatas.Enqueue((asyncGPUReadbackRequest, nativeArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_textureDatas.TryPeek(out var result))
|
||||||
{
|
{
|
||||||
if (obj is { done: true, hasError: false } && this._isRequesting)
|
if (result.Item1.done)
|
||||||
{
|
{
|
||||||
Profiler.BeginSample("Conversion");
|
this._textureDatas.Dequeue();
|
||||||
Bitmap.EncodeToBitmap(_textureData, _bitmapArray, 0, _textureData.Length, _output.width, _output.height);
|
if (result.Item1.hasError)
|
||||||
// var encodeNativeArrayToJPG = ImageConversion.EncodeNativeArrayToJPG(this._textureData, GraphicsFormat.R8G8B8_UNorm, (uint)this._output.width, (uint)this._output.height);
|
{
|
||||||
Profiler.EndSample();
|
Debug.LogError("Request GPU DATA has error");
|
||||||
Profiler.BeginSample("WriteBuffer");
|
}
|
||||||
_process.StandardInput.BaseStream.Write(_bitmapArray, 0, _bitmapArray.Length);
|
else
|
||||||
Profiler.EndSample();
|
{
|
||||||
// encodeNativeArrayToJPG.Dispose();/
|
//放入多线程计算
|
||||||
|
this._doneTextureDatas.Enqueue(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeCaptureThread()
|
private void DisposeCaptureThread()
|
||||||
{
|
{
|
||||||
|
foreach (var textureData in this._textureDatas)
|
||||||
|
{
|
||||||
|
textureData.Item2.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_textureDatas.Clear();
|
||||||
|
foreach (var doneTextureData in this._doneTextureDatas)
|
||||||
|
{
|
||||||
|
doneTextureData.Item2.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_doneTextureDatas.Clear();
|
||||||
|
|
||||||
|
foreach (var nativeArray in this._textureDataPool)
|
||||||
|
{
|
||||||
|
nativeArray.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
if (_process != null && !this._process.HasExited)
|
if (_process != null && !this._process.HasExited)
|
||||||
{
|
{
|
||||||
GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0);
|
GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0);
|
||||||
|
@ -130,11 +190,11 @@ namespace ZC
|
||||||
processStartInfo.Arguments =
|
processStartInfo.Arguments =
|
||||||
$" -probesize 32 -thread_queue_size 5096 -fflags discardcorrupt -flags low_delay -analyzeduration 0 " +
|
$" -probesize 32 -thread_queue_size 5096 -fflags discardcorrupt -flags low_delay -analyzeduration 0 " +
|
||||||
$" -rtbufsize 100M -f dshow -i audio=\"virtual-audio-capturer\" " +
|
$" -rtbufsize 100M -f dshow -i audio=\"virtual-audio-capturer\" " +
|
||||||
$" -f image2pipe -use_wallclock_as_timestamps 1 -i - " +
|
$" -f image2pipe -use_wallclock_as_timestamps 1 -r 60 -i - " +
|
||||||
$" -loglevel info " +
|
$" -loglevel info " +
|
||||||
$" -map 0:a:0 -map 1:v:0 " +
|
$" -map 0:a:0 -map 1:v:0 " +
|
||||||
$" -c:a aac -b:a 128k " +
|
$" -c:a aac " +
|
||||||
$" -c:v:0 libx264 -g 1 -max_delay 0 -vf scale={this._config.resolution} -preset:v ultrafast -tune:v zerolatency -crf 10 -pix_fmt yuv420p -strict -2 " +
|
$" -c:v:0 libx264 -g 1 -bf 0 -max_delay 0 -vf scale={this._config.resolution} -preset:v ultrafast -tune:v zerolatency -crf 10 -pix_fmt yuv420p -strict -2 " +
|
||||||
$" -f flv {this._config.server}{this._config.appName} -bf 0 ";
|
$" -f flv {this._config.server}{this._config.appName} -bf 0 ";
|
||||||
Debug.Log(processStartInfo.Arguments);
|
Debug.Log(processStartInfo.Arguments);
|
||||||
|
|
||||||
|
@ -166,28 +226,38 @@ namespace ZC
|
||||||
throw new NullReferenceException("camera is null");
|
throw new NullReferenceException("camera is null");
|
||||||
if (!camera.targetTexture)
|
if (!camera.targetTexture)
|
||||||
throw new Exception($"The camera[{camera}]'s targetTexture is null;");
|
throw new Exception($"The camera[{camera}]'s targetTexture is null;");
|
||||||
_isRequesting = false;
|
_isRequesting = true;
|
||||||
SetCamera(camera);
|
SetCamera(camera);
|
||||||
_sendData = 0;
|
_sendData = 0;
|
||||||
Object.Destroy(_output);
|
Object.Destroy(_output);
|
||||||
var targetTextureByteCount = camera.targetTexture.width * camera.targetTexture.height * 3;
|
this._targetTextureWidth = camera.targetTexture.width;
|
||||||
|
this._targetTextureHeight = camera.targetTexture.height;
|
||||||
|
var targetTextureByteCount = this._targetTextureWidth * this._targetTextureHeight * 3;
|
||||||
if (_bitmapArray == null || _bitmapArray.Length <
|
if (_bitmapArray == null || _bitmapArray.Length <
|
||||||
Bitmap.FileHeaderSize + Bitmap.ImageHeaderSize + targetTextureByteCount)
|
Bitmap.FileHeaderSize + Bitmap.ImageHeaderSize + targetTextureByteCount)
|
||||||
{
|
{
|
||||||
_bitmapArray = new byte[Bitmap.FileHeaderSize + Bitmap.ImageHeaderSize + targetTextureByteCount];
|
_bitmapArray = new byte[Bitmap.FileHeaderSize + Bitmap.ImageHeaderSize + targetTextureByteCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
_output = new Texture2D(camera.targetTexture.width, camera.targetTexture.height);
|
_output = new Texture2D(this._targetTextureWidth, this._targetTextureHeight);
|
||||||
_textureData.Dispose();
|
|
||||||
_textureData = new NativeArray<byte>(targetTextureByteCount, Allocator.Persistent);
|
|
||||||
CreateCaptureThread();
|
CreateCaptureThread();
|
||||||
|
|
||||||
|
StartCoroutine(this.Test());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IEnumerator Test()
|
||||||
|
{
|
||||||
|
yield return new WaitForSeconds(0.3f);
|
||||||
|
_isRequesting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this._isRequesting = false;
|
this._isRequesting = true;
|
||||||
_sendData = 0;
|
_sendData = 0;
|
||||||
_camera = null;
|
_camera = null;
|
||||||
Object.Destroy(_output);
|
Object.Destroy(_output);
|
||||||
|
|
|
@ -102,3 +102,14 @@
|
||||||
2024/11/18 14:13:55 [notice] 27524#13420: sockinit() attempting to access sockapi
|
2024/11/18 14:13:55 [notice] 27524#13420: sockinit() attempting to access sockapi
|
||||||
2024/11/18 14:13:55 [notice] 27524#13420: Access to sockapi succeded!
|
2024/11/18 14:13:55 [notice] 27524#13420: Access to sockapi succeded!
|
||||||
2024/11/18 14:13:55 [notice] 27524#13420: using sockapi from "4;80;"
|
2024/11/18 14:13:55 [notice] 27524#13420: using sockapi from "4;80;"
|
||||||
|
2024/11/18 14:30:25 [notice] 12968#16444: Fatal: wait for sockapi failed
|
||||||
|
2024/11/18 14:30:28 [error] 27524#15664: *8 live: already publishing, client: 127.0.0.1, server: 0.0.0.0:1935
|
||||||
|
2024/11/18 14:30:39 [notice] 30504#8424: Fatal: wait for sockapi failed
|
||||||
|
2024/11/18 14:31:04 [notice] 1792#18588: Fatal: wait for sockapi failed
|
||||||
|
2024/11/18 16:17:32 [error] 27524#15664: *42 live: already publishing, client: 127.0.0.1, server: 0.0.0.0:1935
|
||||||
|
2024/11/18 16:19:00 [error] 27524#15664: *43 live: already publishing, client: 127.0.0.1, server: 0.0.0.0:1935
|
||||||
|
2024/11/18 16:21:20 [error] 27524#15664: *44 live: already publishing, client: 127.0.0.1, server: 0.0.0.0:1935
|
||||||
|
2024/11/18 16:24:23 [error] 27524#15664: *45 live: already publishing, client: 127.0.0.1, server: 0.0.0.0:1935
|
||||||
|
2024/11/18 16:25:49 [error] 27524#15664: *46 live: already publishing, client: 127.0.0.1, server: 0.0.0.0:1935
|
||||||
|
2024/11/18 16:27:59 [error] 27524#15664: *47 live: already publishing, client: 127.0.0.1, server: 0.0.0.0:1935
|
||||||
|
2024/11/18 16:30:06 [error] 27524#15664: *48 live: already publishing, client: 127.0.0.1, server: 0.0.0.0:1935
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
cd client-pull
|
cd client-pull
|
||||||
ffplay.exe rtmp://127.0.0.1:1935/live/test1 -fflags nobuffer -probesize 512 -analyzeduration 0 -flags low_delay -flags2 fast -fflags discardcorrupt
|
ffplay.exe rtmp://127.0.0.1:1935/live/test1 -fflags nobuffer -probesize 2048 -analyzeduration 0 -flags low_delay -flags2 fast -fflags discardcorrupt
|
Loading…
Reference in New Issue