我想在iOS上用net7.0解析MJPEG流。我目前的解决方案是在windows和android上工作,但在iOS上我无法接收原始流。
何为MJPEG?MJPEG是一个恒定的jpeg流。内容以标题信息开始,之后是图像,然后在内容中再次出现标题。响应示例:
Content-Type: image/jpeg
Content-Length: 50706
{Image}
Content-Type: image/jpeg
Content-Length: 50750
{Image2}
在标题之后,总是有两个换行符。因此,我解析响应以找到2个换行符,解析Content-Lenght
并知道,下面的50706字节是图像。我从流解析中提取图像,以获取接下来的2个换行符,等等。
HttpResponseMessage headerResponse = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Stream stream = headerResponse.Content.ReadAsStreamAsync();
{Parsing stuff} ...
我现在的问题是,iOS没有交出原始内容。内容流中没有标头信息。是这样的:
{Image}
{Image2}
所以我没有办法将流分割成单个图像。headerResponse.Content.GetContentLegth()
不是正确的值,在解析流时也没有改变。我已经尝试了不同的httpclient (NSUrlSessionHandler, CFNetworkHandler)
所以有一种方法,以防止iOS从内容流中删除标题信息?
它现在运行,感谢我在互联网上找到的2个帖子:Swift Mjpeg流只显示单帧和下载文件的Xamarin iOS在后台
我的解决方案是两者的混合
public class MyImageReceiver
{
public async Task Get(string requestUri, Action<IEnumerable<byte>> imageReceived, int timeout, CancellationToken cancellationToken)
{
bool isTimeout = false;
DateTime lastImageReceivedAt = DateTime.Now;
var config = NSUrlSessionConfiguration.DefaultSessionConfiguration;
using (NSUrl nsUrl = NSUrl.FromString(requestUri))
using (NSMutableUrlRequest request = new NSMutableUrlRequest(nsUrl,
cachePolicy: NSUrlRequestCachePolicy.ReloadIgnoringLocalCacheData,
timeoutInterval: 2000))
using (CameraPictureClientSessionDelegate cameraPictureClientSessionDelegate = new CameraPictureClientSessionDelegate())
using (NSUrlSession session = NSUrlSession.FromConfiguration(config, cameraPictureClientSessionDelegate, null))
using (NSUrlSessionDataTask streamingTask = session.CreateDataTask(request: request))
{
request.HttpMethod = "GET";
cameraPictureClientSessionDelegate.ImageReceived += (s, e) =>
{
lastImageReceivedAt = DateTime.Now;
_waitHandle.Release();
imageReceived(e);
};
streamingTask.Resume();
do
{
await _waitHandle.WaitAsync(timeout, cancellationToken);
if ((DateTime.Now - lastImageReceivedAt).TotalMilliseconds > timeout)
{
isTimeout = true;
}
}
while ((streamingTask.State == NSUrlSessionTaskState.Running
|| streamingTask.State == NSUrlSessionTaskState.Suspended)
&& !cancellationToken.IsCancellationRequested
&& !isTimeout);
if (streamingTask.State == NSUrlSessionTaskState.Running
|| streamingTask.State == NSUrlSessionTaskState.Suspended)
{
streamingTask.Cancel();
}
if (isTimeout)
{
throw new TimeoutException();
}
}
}
}
和委托
public class CameraPictureClientSessionDelegate :
NSUrlSessionDataDelegate,
INSUrlSessionTaskDelegate
{
private SemaphoreSlim _semaphore = new SemaphoreSlim(1);
public event EventHandler<IEnumerable<byte>> ImageReceived;
List<byte> _data = new List<byte>();
public override void DidReceiveData(NSUrlSession session, NSUrlSessionDataTask dataTask, NSData data)
{
try
{
_semaphore.Wait();
_data.AddRange(data.ToArray());
}
finally
{
_semaphore.Release();
}
}
public override void DidReceiveResponse(NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action<NSUrlSessionResponseDisposition> completionHandler)
{
try
{
_semaphore.Wait();
if (_data.Count > 0)
{
ImageReceived?.Invoke(this, _data);
_data = new List<byte>();
}
}
finally
{
_semaphore.Release();
}
completionHandler(NSUrlSessionResponseDisposition.Allow);
}
}