【问题标题】:How to send an image(UIImage) from a SWIFT ios client program to a JAVA server program?如何将图像(UIImage)从 SWIFT ios 客户端程序发送到 JAVA 服务器程序?
【发布时间】:2017-09-02 08:14:19
【问题描述】:

我的问题:

  • 如何正确地将图像从 ios swift 客户端应用程序转换并发送到 java 服务器?(不使用大量外部 sdk)

  • 在 swift 中使用哪种类型的套接字(我是 swift 新手,真的找不到任何合适的套接字)?

  • 请给我一个示例代码,因为我完全不熟悉 swift 语法和库。

我的程序的预期结果 - ios swift 应用程序应该有效地连接到我的 java 服务器并将视频帧的图像实时发送到它。然后图像应在 Java 服务器计算机上转换为 bufferedImage 并作为视频播放!

关于之前提出的问题 - 我只发现了一个类似的问题,但答案不是很丰富。

详情

  • 所以,我在我的 mac 上编写了一个 Java 服务器程序,我想添加一个功能,用户应该能够从他的 iPhone(ios 设备)将实时视频源发送到我的 Java 服务器程序。

  • ios 应用是在 xcode 上用 swift 编写的。

  • 为了做到这一点,我从 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) 将此字节 [] 数据发送到运行我的 Java 服务器的 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 服务器程序生成一个 MobileServer 线程来处理这个客户端。 DataInput 和 OutputStreams 使用 ServerSocket 打开。这是 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,因此在查看存储在文件中的“视频”时,我只能看到一条“图像”,而iPhone 捕捉良好。(基本上图像没有从 ios 应用程序正确传输到我的服务器!)

整个 Swift 程序的 viewController.swift 用于视频捕获都来自这个 github 项目 (https://github.com/FlexMonkey/LiveCameraFiltering)

编辑 - 我已经找出问题并将其发布为答案,但这仍然只是一种解决方法,因为服务器视频源仍然挂起很多,我不得不降低发送的图像字节数据的质量由迅速的客户。肯定有更好的做事方式,我要求人们分享他们的知识。

【问题讨论】:

    标签: java swift video-streaming client-server uiimagejpegrepresentation


    【解决方案1】:

    所以,我无法为上述问题找到一个完整且绝对完美的解决方案,但为了任何其他可能偶然发现类似跨语言客户端-服务器程序问题的快速初学者的利益,这里是我的两美分:

    上述代码中的第一个也是最重要的错误是这一行:

     let data = UIImageJPEGRepresentation(image, 1.0)
    

    1) 在这里,我通过将压缩因子设置为 1 来将 UIImage 编码为尽可能高的质量。正如我后来检查的那样,这会导致创建计数超过 100000 的字节数组,因此很难通过 TCPClient 套接字轻松快速地发送如此大的数据。

    2) 即使TCPClient socket高效发送这么大的数组,服务器端的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 将一次接收一个字符并从中形成一个字符串,直到遇到终止字符“%”,然后此 base64 字符串将转换为字节数组:

      imageBytes=javax.xml.bind.DatatypeConverter.parseBase64Binary(str); 
      //str is a String formed by concatenating characters received by the InputStreamReader 
      
    • 这个imageBytes数组然后被转换成一个BufferedImage,然后在一个面板上一个接一个地绘制,从而再现了原来的iPhone直播视频

    修改后的 Swift 代码(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 服务器端 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(); }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
            }
    
    • 这是正在播放的视频的屏幕截图:

    • 但正如您所见,由于 swift 客户端发送的图像质量非常差:生成的视频质量也很差。此外,视频仍然在两者之间挂起..

    • 我确信会有更好的方法从 swift 套接字发送更高质量的图像数据,市场上所有这些视频聊天应用程序都证明了这一点,如果有人能对高级技术有所了解,我会很高兴实现高清图像传输所涉及的方法

    • 一种方法是在客户端和服务器端缓存字节数据,以传输和播放更高质量的 jpeg 数据

    【讨论】:

    • 我可以使用 Swift Socket(跨平台)传输视频吗?例如iOS 到 android,反之亦然?
    猜你喜欢
    • 2018-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-14
    • 1970-01-01
    • 2019-03-12
    • 1970-01-01
    相关资源
    最近更新 更多