我的问题:
如何正确地将图像从ios swift客户端应用程序转换并发送到java服务器?(不使用大量外部sdks)
在swift中使用哪种类型的套接字(我是swift的新手,无法找到合适的套接字)?
请给我一个示例代码,因为我完全不熟悉swift语法和库。
我的计划的预期结果 - ios swift应用程序应该有效地连接到我的java服务器并将视频帧的图像直播到它。然后,图像应在Java服务器计算机上转换为bufferedImage,并作为视频播放!
关于以前提出的问题 - 我发现只有一个类似的问题,但答案并不是很有用。
详情
所以,我在我的mac上写了一个Java服务器程序,我想添加一个功能,用户应该能够从他的iPhone(ios设备)向我的Java服务器程序发送实时视频。
ios应用程序是用xcode快速编写的。
为了做到这一点,我从swift程序中的视频帧中捕获 CGImage 并将其转换为 UIImage ;然后我将这个UIImage转换为byte []数据,如下所示: -
let cgImage:CGImage = context.createCGImage(cameraImage, from:
cameraImage.extent)! //cameraImage is grabbed from video frame
image = UIImage.init(cgImage: cgImage)
let data = UIImageJPEGRepresentation(image, 1.0)
然后使用SwiftSocket / TCPClient(https://github.com/swiftsocket/SwiftSocket)将此byte []数据发送到运行Java Server的IP地址和端口
client?.send(data: data!)
这里的客户端是TCPClient类型的对象,它在swift xcode中声明如下:(https://github.com/swiftsocket/SwiftSocket/blob/master/Sources/TCPClient.swift)
client = TCPClient(address: host, port: Int32(port))
client?.connect(timeout: 10)
连接成功,Java Server程序生成一个MobileServer线程来处理此客户端。使用ServerSocket打开DataInput和OutputStream。这是由Java服务器生成的MobileServer线程的run()方法(其中“in”是从ServerSocket派生的DataInputStream)
public void run()
{
try{
while(!stop)
{
int count=-1;
count = in.available();
if(count>0) System.out.println("LENGTH="+count);
byte[] arr=new byte[count];
System.out.println("byte="+arr);
in.read(arr);
BufferedImage image=null;
try{
InputStream inn = new ByteArrayInputStream(arr);
image = ImageIO.read(inn);
inn.close();
}catch(Exception f){ f.printStackTrace();}
System.out.println("IMaGE="+image);
if(image!=null)
appendToFile(image);
}
}catch(Exception l){ l.printStackTrace(); }
}
问题是我的Java服务器正在接收一些奇怪的字节序列,这些字节序列可能无法正确转换为BufferedImage,因此在查看存储在文件中的“Video”时,我只能看到一条细长的“图像”。 iPhone捕捉得很好。(基本上图像没有从ios应用程序正确传输到我的服务器!)
用于视频捕获的整个Swift程序的viewController.swift派生自此github项目 (https://github.com/FlexMonkey/LiveCameraFiltering)
修改 - 我已经找到了问题,并将其作为答案发布,但这仍然只是一种解决方法,因为服务器视频源仍然很多,我不得不降低图像字节数据的质量快速的客户。绝对有更好的办法,我要求人们分享他们的知识。
答案 0 :(得分:1)
所以,我无法为上述问题找到一个完整且绝对完美的解决方案,但为了任何其他可能偶然发现类似跨语言客户端 - 服务器程序问题的快速初学者的好处,这里是我的两分钱:
上述代码中的首要错误是这一行:
let data = UIImageJPEGRepresentation(image, 1.0)
1)在这里,我通过将压缩因子提供为1来将UIImage编码为最高质量。这,正如我后面检查的那样导致创建计数超过100000的字节数组,因此很难通过TCPClient套接字轻松快速地发送如此大的数据。
2)即使TCPClient套接字有效地发送了这么大的数组,服务器端的Java DataInputStream也很难一次读取完整的数据。它可能一次只读取一小块数据,因此在java服务器端生成的图像是局部和模糊的。
3)这一行是另一个问题:
count = in.available();
if(count>0) System.out.println("LENGTH="+count);
byte[] arr=new byte[count];
System.out.println("byte="+arr);
in.read(arr);
in.available()方法可能不会返回客户端发送的完整长度数据。这导致读取不完整的字节数据,从而导致图像不完整。
解决方案/解决方法(种类)
我在swift客户端的UIImageJPEGRepresentation()方法中将压缩因子减少到大约0.000005,这导致创建长度大约为5000的字节数组(这是可管理的)
为了避免在服务器端读取不完整数据的问题,我将字节数组转换为base64String,然后我只在该字符串末尾添加了一个终止字符“%”,在服务器端将标记一个base64字符串的结尾。
我将服务器端的DataInputStream / DataOutputStream更改为InputStreamReader / OutputStreamWriter,因为我现在处理的是字符/字符串。
Java服务器的InputStreamReader一次只接受一个字符并形成一个字符串,直到它遇到终止字符“%”,然后这个base64string将转换为字节数组:
imageBytes=javax.xml.bind.DatatypeConverter.parseBase64Binary(str);
//str is a String formed by concatenating characters received by the InputStreamReader
然后将此imageBytes数组转换为BufferedImage,然后逐个在面板上绘制,从而再现原始iPhone直播视频
修改后的Swift Code(ios客户端)
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!)
{
var cameraImage: CIImage
var image: UIImage ;
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
cameraImage = CIImage(cvPixelBuffer: pixelBuffer!)
let context:CIContext = CIContext.init(options: nil)
let cgImage:CGImage = context.createCGImage(cameraImage, from: cameraImage.extent)!
image = UIImage(cgImage: cgImage)
DispatchQueue.main.async
{
self.imageView.image = image //live video captured from camera streamed to the device's own UIImageView
}
let thumbnail = resizeImage(image: image, targetSize: CGSize.init(width: 400, height: 400)) // snapshot image from camera resized
let data = UIImageJPEGRepresentation(thumbnail,0.000005) //the snapshot image converted into byte data
let base64String = data!.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))
// byte image data is encoded to a base64String
var encodeImg=base64String.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed )
encodeImg = encodeImg! + String("%") // termination char % is added at the end
var sendData = String("0%")
if(live)
{
sendData = encodeImg!
}
client?.send(string: sendData!) //sent as a String using TCPClient socket
}
修改了MobileServer Thread类的Java Server Side run()方法
public void run()
{
try{
boolean access_granted=false;
while(!stop)
{
char chr=(char)in.read();
if(chr!='%') // read and append char by char from the InputStreamReader "in" until it encounters a '%'
str+=Character.toString(chr);
else terminate=true;
if(terminate)
{
if(entry)
{
int a=str.indexOf('&');
int b=str.indexOf('#');
String username=str.substring(0,a);
String password=str.substring((a+1),b);
String ip=str.substring((b+1),str.length());
System.out.println("IP ADDRESS: \""+ ip+"\"");
String usernameA[]=convertToArray(username);
String passwordA[]=convertToArray(password);
String user=decrypt(usernameA,portt);
String pass=decrypt(passwordA,portt);
boolean accessGranted=false;
int response=dbManager.verify_clientLogin(user,pass);
if(response==RegisterInfo.ACCESS_GRANTED) {
System.out.println("access granted");
accessGranted=true;
}
int retInt=-1;
if(accessGranted) retInt=1;
out.write(retInt);
entry=false;
terminate=false;
}
else
{
terminate=false;
try {
// str includes the original single base64String produced by the swift client app which is converted back to a byte array
imageBytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(str);
}catch(ArrayIndexOutOfBoundsException l){ exception=true; }
str="";
if(!exception)
{
//this byte array image data is converted to a image and played on the videoPlayer, and serial images played would be visible as a video stream
vidPlayer.playImage(imageBytes);
ioexcep=false;
}
else exception=false;
}
}
}
}catch(Exception l){ l.printStackTrace(); }
}