从Swift应用程序向PHP服务器发送音频时,音频会丢失
我正在用Swift制作一个应用程序,它会录制一些音频,然后将这些音频发送到我的PHP服务器 应用程序可以很好地录制音频片段(可以毫无问题地播放)。当我从Swift应用程序向PHP服务器发送音频时,音频会丢失,php,ios,audio,swift,Php,Ios,Audio,Swift,我正在用Swift制作一个应用程序,它会录制一些音频,然后将这些音频发送到我的PHP服务器 应用程序可以很好地录制音频片段(可以毫无问题地播放)。当我println录制的音频剪辑时,它会显示加载和加载的字节数据(当我将音频放入NSData包装器时也是如此)。这一切对我来说都表明,应用程序内部的音频很好 在我的服务器上捕获录制的PHP文件也可以正常工作,没有错误 但在线路的某个地方,录制的音频片段丢失了 上载录音的Swift代码: // The variable "recordedFileURL"
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()
$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
中应接收录制的PHP代码:
// 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()
$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
根本没有收到任何东西
我发送的录音是错误的,还是捕捉到的录音是错误的?或者两者都有
提前感谢。您的PHP代码基本上很好。
$\u文件
部分正常,但
问题在于如何在Swift代码中生成HTTP请求。主要是HTTP头。
为多部分数据创建标头时,模式如下(如果我们选择AAAA作为边界):
- 我们选择的边界:“AAAA”
- Content Type=“多部分/表单数据;边界=AAAA”
- 起始Bounary=--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网络数据。我个人喜欢,因为它简单,但你也可以使用或
我刚刚运行了您的代码,并将HTTP通信量与使用curl发出请求时发生的情况进行了比较
curl -X POST http://localhost/\~cjwirth/catch.php -F "file=@Untitled.m4a"
这是swift 4~5代码。 在ViewController上创建一个新按钮(代码中的按钮标签),并将按钮操作链接到@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()
}
}
导入UIKit
进口AVF基金会
类ViewController2:UIViewController、AVAudioRecorderDelegate{
var recordingSession:AVAudioSession!
var录音机:AVAudioRecorder!
var audioPlayer:AVAudioPlayer!
var numberOfRecords=0
@IBVAR按钮标签:UIButton!
让E_401=“E_401”
让数据库_路径=”http:///YourPrjectName/"
重写func viewDidLoad(){
super.viewDidLoad()
//设置识别器以识别按钮动作
让longPressRecognizer=UILongPressGestureRecognizer(目标:自我,操作:#选择器(记录))
longPressRecognizer.minimumPressDuration=0
按钮标签。添加手势识别器(LongPress识别器)
//设置会话
recordingSession=AVAudioSession.sharedInstance()
//从用户获得使用麦克风的权限
AVAudioSession.sharedInstance().requestRecordPermission{(hasPermission)位于
如果得到允许
{打印(“接受”)}
}
}
@iAction func记录(\uGestureRecognitor:UILongPressGestureRecognitor){
//检查是否有活动的记录器
如果(gestureRecognizer.state==开始)和(audioRecorder==无){
//每进行一次新录制,录制总数增加+1
self.numberOfRecords+=1
//设置文件名和设置
让filename=getDirectory().appendingPathComponent(“\(numberOfRecords).m4a”)
让设置=[
AVFormatIDKey:Int(kaudioformampeg4aac),
AVE密钥:12000,
AVNumberOfChannelsKey:1,
AVEncoderAudioQualityKey:AVAudioQuality.medium.rawValue
]
做
{
//开始录音
buttonLabel.setTitle(“录制…”,用于:。正常)
audioRecorder=试用AVAudioRecorder(url:文件名,设置:设置)
audioRecorder.delegate=self
录音机
}
抓住
{
//捕捉错误
displayAlert(标题:“Oops!”,消息:“录制失败”)
}
}如果gestureRecognizer.state==.end&&(录音机!=nil),则为else
{
//停止录音
buttonLabel.setTitle(“开始录制”,用于:。正常)
录音机
录音机=零
做{
让filename=getDirectory().appendingPathComponent(“\(numberOfRecords).m4a”)
让录制:NSData=尝试NSData(内容:文件名)
上传文件(文件数据:记录为数据,文件名:“\(numberOfRecords).m4a”){
(fileURL,e)在
如果e==nil{
打印(“文件URL:+fileURL!)
}
}
}抓住{
打印(“意外错误:\(错误)”)
}
}
}
//获取目录路径的函数
func getDirectory()->URL
{
让路径=FileManager.default.url(对于:.documentDirectory,在:.userDomainMask中)
让documentDirectory=路径[0]
返回文档目录
}
//显示警报的函数
func displayAlert(标题:字符串,消息:字符串)
{
let alert=UIAlertController(标题:标题,消息:消息,首选样式:。警报)
addAction(UIAlertAction(标题:“dismise”,样式:。默认,处理程序:nil))
当前(警报、动画:真、完成:无)
}
奉承