【问题标题】:Sending audio from a Swift App to PHP Server, and somewhere the audio is lost将音频从 Swift 应用程序发送到 PHP 服务器,并且音频丢失了
【发布时间】:2015-03-07 22:19:44
【问题描述】:

我正在用 Swift 制作一个应用程序来录制一些音频,然后将录音发送到我的 PHP 服务器。

应用程序可以很好地录制音频片段(可以毫无问题地播放)。当我println 录制的音频剪辑时,它会显示字节数据的负载和负载(当我将音频放入NSData 包装器时也是如此)。这一切都表明应用程序内的音频很好。

在我的服务器上捕获记录的 PHP 文件也可以正常工作并且没有错误。

但是在某处录制的音频片段丢失了。

上传录音的 Swift 代码:

// The variable "recordedFileURL" is defined earlier in the code like this:

currentFilename = "xxxx.m4a"
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let docsDir: AnyObject=dirPaths[0]
recordedFilePath = docsDir.stringByAppendingPathComponent(self.currentFilename)
recordedFileURL = NSURL(fileURLWithPath: self.recordedFilePath)

// "currentFilename", "recordedFilePath" and "recordedFileURL" are all global variables

// This recording stored at "recordedFileURL" can be played back fine.

let sendToPath = "http://......../catch.php"
let sendToURL = NSURL(string: sendToPath)
let recording: NSData? = NSData(contentsOfURL: recordedFileURL)
let boundary = "--------14737809831466499882746641449----"
let contentType = "multipart/form-data;boundary=\(boundary)"

var request = NSMutableURLRequest()
request.URL = sendToURL
request.HTTPMethod = "POST"
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
request.addValue(recId, forHTTPHeaderField: "REC-ID") // recId is defined elsewhere

var body = NSMutableData()
var header = "Content-Disposition: form-data; name=\"\(currentFilename)\"; filename=\"\(recordedFilePath)\"\r\n"

body.appendData(("\r\n-\(boundary)\r\n" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData((header as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(("Content-Type: application/octet-stream\r\n\r\n" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)

body.appendData(recording!) // adding the recording here

body.appendData(("\r\n-\(boundary)\r\n" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)

request.HTTPBody = body

var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in

    println("upload complete")
    let dataStr = NSString(data: data, encoding: NSUTF8StringEncoding)
    println(dataStr)

})

task.resume()

文件catch.php中应该接收录音的PHP代码:

$contents = file_get_contents('php://input');
$files = $_FILES;

echo "Caught the following:/r/n";
echo "Contents:" . var_export($contents) . "/r/n";
echo "Files:" . var_export($files) . "/r/n";

每当我运行所有这些时,我都会从catch.php 得到以下输出:

Caught the following:
Contents:''
Files:array (
)

所以catch.php 根本没有收到任何东西。

是我发送录音错误,还是捕捉录音错误?还是两者兼有?

提前致谢。

【问题讨论】:

  • 您确定recording 变量在您创建body.appendData(recording!) 时包含数据吗?在调试器中检查它或将长度放入 NSLog 以确保...
  • 你检查过 php.ini 中的 max_upload_size。如果您尝试上传大于该值的文件可能会导致此问题
  • 感谢您的回复! php.ini max_upload_size 设置为 20Mb(对于我们 1-10 秒的记录来说应该足够了)——是的,记录变量包含大量数据——当我 NSLog 时,调试窗口中充满了数据——它太多了,我必须滚动浏览它
  • var_dump($contents); 对您有帮助吗?
  • "body.appendData(("\r\n!!-!!(boundary)\r\n" - 额外的"-"这里

标签: php ios audio swift


【解决方案1】:

您的 PHP 代码基本没问题。 $_FILES 部分没问题,但 php://input is not available with enctype="multipart/form-data".

问题在于您如何在 Swift 代码中生成 HTTP 请求。主要是 HTTP 标头。 在为多部分数据创建标头时,模式是这样的(如果我们选择 AAAAA 作为我们的边界):

  • 我们选择的边界:“AAAAA”
  • 内容类型 = "multipart/form-data;boundary=AAAAA"
  • 开始边界 = --AAAAA
  • 结束边界 = --AAAAA--

所以通过稍微修改你的代码:

// This was your main problem
let boundary = "--------14737809831466499882746641449----"
let beginningBoundary = "--\(boundary)"
let endingBoundary = "--\(boundary)--"
let contentType = "multipart/form-data;boundary=\(boundary)"

// recordedFilePath is Optional, so the resulting string will end up being 'Optional("/path/to/file/filename.m4a")', which is wrong.
// We could just use currentFilename if we wanted
let filename = recordedFilePath ?? currentFilename
var header = "Content-Disposition: form-data; name=\"\(currentFilename)\"; filename=\"\(recordedFilePath!)\"\r\n"

var body = NSMutableData()
body.appendData(("\(beginningBoundary)\r\n" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData((header as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(("Content-Type: application/octet-stream\r\n\r\n" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(recording!) // adding the recording here
body.appendData(("\r\n\(endingBoundary)\r\n" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)

var request = NSMutableURLRequest()
request.URL = sendToURL
request.HTTPMethod = "POST"
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
request.addValue(recId, forHTTPHeaderField: "REC-ID") // recId is defined elsewhere
request.HTTPBody = body

如果你再次遇到这样的事情,在调试这样的网络代码时,我喜欢使用让我检查正在传输的 HTTP 网络数据的工具。我个人喜欢HTTPScoop,因为它很简单,但你也可以使用BurpSuiteCharles

我刚刚运行了您的代码,并将 HTTP 流量与我使用 curl 发出请求时发生的情况进行了比较

curl -X POST http://localhost/\~cjwirth/catch.php -F "file=@Untitled.m4a"

【讨论】:

    【解决方案2】:

    这里是 swift 4 ~ 5 代码。 在 ViewController 上创建新的按钮(代码中的 buttonLabel)并将按钮操作链接到 @IBAction 和 @IBOutlet [按住重新编码,松开上传音频文件]

    import UIKit
    import AVFoundation
    
    class ViewController2: UIViewController, AVAudioRecorderDelegate{
        
        var recordingSession: AVAudioSession!
        var audioRecorder: AVAudioRecorder!
        var audioPlayer: AVAudioPlayer!
        var numberOfRecords = 0
        
    
        @IBOutlet weak var buttonLabel: UIButton!
        let E_401 = "E_401"
        let DATABASE_PATH = "http://<IP_address_of_PHP_server>/YourPrjectName/"
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // Set the recognizer to recognize the button action
            let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(record))
            longPressRecognizer.minimumPressDuration = 0
            buttonLabel.addGestureRecognizer(longPressRecognizer)
            
            // Setting up session
            recordingSession = AVAudioSession.sharedInstance()
            
            
            // Get permission from user to use mic
            AVAudioSession.sharedInstance().requestRecordPermission{ (hasPermission) in
                if hasPermission
                {print ("ACCEPTED")}
            }
        }
        
        
        @IBAction func record(_ gestureRecognizer: UILongPressGestureRecognizer) {
            
            // Check if we have an active recorder
            if (gestureRecognizer.state == .began) && (audioRecorder == nil) {
                // Increase +1 total number of recordings for every new recording made
                
                self.numberOfRecords += 1
                
                // Setting filename and settings
                let filename = getDirectory().appendingPathComponent("\(numberOfRecords).m4a")
                let settings = [
                    AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                    AVSampleRateKey: 12000,
                    AVNumberOfChannelsKey: 1,
                    AVEncoderAudioQualityKey: AVAudioQuality.medium.rawValue
                ]
                
                do
                {
                    // Start audio recording
                    buttonLabel.setTitle("Recording...", for: .normal)
                    audioRecorder = try AVAudioRecorder(url: filename, settings: settings)
                    audioRecorder.delegate = self
                    audioRecorder.record()
                }
                catch
                {
                    // Catch for errors
                    displayAlert(title: "Oops!", message: "Recording failed")
                    
                }
                
            } else if gestureRecognizer.state == .ended && (audioRecorder != nil)
                
            {
                // Stopping audio recording
                buttonLabel.setTitle("Start Recording", for: .normal)
                audioRecorder.stop()
                audioRecorder = nil
                
                
                
                do {
                    let filename = getDirectory().appendingPathComponent("\(numberOfRecords).m4a")
                    let recording: NSData = try NSData(contentsOf: filename)
                    self.uploadFile(fileData: recording as Data, fileName: "\(numberOfRecords).m4a"){
                        (fileURL, e) in
                        if e == nil {
                            print("FILE URL: " + fileURL!)
                        }
                    }
                    
                } catch {
                    print("Unexpected <<<<<<<<<<<<<<>>>>>>>>>>>>>> error: \(error)")
                }
    
            }
        }
        
        // Function that gets path to directory
        func getDirectory () -> URL
        {
            let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
            let documentDirectory = paths[0]
            
            return documentDirectory
        }
        
        // Function that displays an alert
        func displayAlert(title:String, message:String)
        {
            let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "dismiss", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)
        }
        
        
        func uploadFile(fileData:Data, fileName:String , completion: @escaping (_ fileURL:String?, _ error:String?) -> Void) {
            let recId = "\(numberOfRecords)"
            print("FILENAME: \(fileName)")
            let request = NSMutableURLRequest()
    
            let boundary = "--------14737809831466499882746641449----"
            let beginningBoundary = "--\(boundary)"
            let endingBoundary = "--\(boundary)--"
            let contentType = "multipart/form-data;boundary=\(boundary)"
            
            
            
            request.url = URL(string: DATABASE_PATH + "catch.php")
    //        catch.php is php script on server
            request.httpShouldHandleCookies = false
            request.timeoutInterval = 60
            request.httpMethod = "POST"
            request.setValue(contentType, forHTTPHeaderField: "Content-Type")
            let body = NSMutableData()
            body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
            body.append("Content-Disposition: form-data; name=\"fileName\"\r\n\r\n".data(using: String.Encoding.utf8)!)
            body.append("\(fileName)\r\n".data(using: String.Encoding.utf8)!)
            body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
            body.append("Content-Disposition: form-data; name=\"file\"; filename=\"file\"\r\n".data(using: String.Encoding.utf8)!)
            
            body.append(("\(beginningBoundary)\r\n" as NSString).data(using: String.Encoding.utf8.rawValue)!)
            body.append(("Content-Type: application/octet-stream\r\n\r\n" as NSString).data(using: String.Encoding.utf8.rawValue)!)
    
            body.append(fileData)
            body.append("\r\n".data(using: String.Encoding.utf8)!)
            
            
            body.append("--\(boundary)--\r\n".data(using: String.Encoding.utf8)!)
            request.addValue(contentType, forHTTPHeaderField: "Content-Type")
    //        request.addValue(recId, forHTTPHeaderField: "REC-ID")
            request.httpBody = body as Data
            
            
            let session = URLSession.shared
            let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
                guard let _:Data = data as Data?, let _:URLResponse = response, error == nil else {
                    DispatchQueue.main.async { completion(nil, error!.localizedDescription) }
                    return
                }
                if let response = String(data: data!, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) {
                    print("XSUploadFile -> RESPONSE: " + self.DATABASE_PATH + response)
                    DispatchQueue.main.async { completion(self.DATABASE_PATH + response, nil) }
                    
                    // NO response
                } else { DispatchQueue.main.async { completion(nil, self.E_401) } }// ./ If response
            }; task.resume()
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-01-07
      • 2016-12-11
      • 1970-01-01
      • 2013-08-26
      • 1970-01-01
      • 1970-01-01
      • 2022-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多