【问题标题】:Recording audio in Swift用 Swift 录制音频
【发布时间】:2014-12-15 20:30:18
【问题描述】:

有谁知道我在哪里可以找到有关如何在 Swift 应用程序中录制音频的信息?我一直在查看一些音频播放示例,但似乎无法找到有关实现音频录制的任何内容。谢谢

【问题讨论】:

    标签: audio swift


    【解决方案1】:

    在 Swift 3 中

    • 添加框架 AVFoundation
    • **在 info.plist 添加键值

    Key = 隐私 - 麦克风使用说明和 = 用于使用 麦克风

    (如果您不提供该值,应用程序将崩溃 - 说明您请求权限的原因)**

    • 导入 AVFoundation & AVAudioRecorderDelegate、AVAudioPlayerDelegate

      import AVFoundation
      
       class RecordVC: UIViewController , AVAudioRecorderDelegate, AVAudioPlayerDelegate
      
    • 创建用于录制音频和播放音频的按钮,以及用于显示录制时间的标签,并将出口和操作指定为 start_recording , play_recording 并声明一些我们稍后将使用的变量

      @IBOutlet var recordingTimeLabel: UILabel!
      @IBOutlet var record_btn_ref: UIButton!
      @IBOutlet var play_btn_ref: UIButton!
      
      var audioRecorder: AVAudioRecorder!
      var audioPlayer : AVAudioPlayer!
      var meterTimer:Timer!
      var isAudioRecordingGranted: Bool!
      var isRecording = false
      var isPlaying = false
      
    • 在viewDidLoad中检查记录权限

      override func viewDidLoad() {
          super.viewDidLoad()
          check_record_permission()
      }
      
      func check_record_permission()
      {
          switch AVAudioSession.sharedInstance().recordPermission() {
          case AVAudioSessionRecordPermission.granted:
              isAudioRecordingGranted = true
              break
          case AVAudioSessionRecordPermission.denied:
              isAudioRecordingGranted = false
              break
          case AVAudioSessionRecordPermission.undetermined:
              AVAudioSession.sharedInstance().requestRecordPermission({ (allowed) in
                      if allowed {
                          self.isAudioRecordingGranted = true
                      } else {
                          self.isAudioRecordingGranted = false
                      }
              })
              break
          default:
              break
          }
      }
      
    • 生成要将录音保存为 myRecording.m4a 的路径

      func getDocumentsDirectory() -> URL
      {
          let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
          let documentsDirectory = paths[0]
          return documentsDirectory
      }
      
      func getFileUrl() -> URL
      {
          let filename = "myRecording.m4a"
          let filePath = getDocumentsDirectory().appendingPathComponent(filename)
      return filePath
      }
      
    • 设置录音机

      func setup_recorder()
      {
          if isAudioRecordingGranted
          {
              let session = AVAudioSession.sharedInstance()
              do
              {
                  try session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)
                  try session.setActive(true)
                  let settings = [
                      AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                      AVSampleRateKey: 44100,
                      AVNumberOfChannelsKey: 2,
                      AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue
                  ]
                  audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings)
                  audioRecorder.delegate = self
                  audioRecorder.isMeteringEnabled = true
                  audioRecorder.prepareToRecord()
              }
              catch let error {
                  display_alert(msg_title: "Error", msg_desc: error.localizedDescription, action_title: "OK")
              }
          }
          else
          {
              display_alert(msg_title: "Error", msg_desc: "Don't have access to use your microphone.", action_title: "OK")
          }
      }
      
    • 按下 start_recording 按钮开始录音并使用 updateAudioMeter 显示秒数,如果录音开始则结束录音

      @IBAction func start_recording(_ sender: UIButton)
      {
          if(isRecording)
          {
              finishAudioRecording(success: true)
              record_btn_ref.setTitle("Record", for: .normal)
              play_btn_ref.isEnabled = true
              isRecording = false
          }
          else
          {
              setup_recorder()
      
              audioRecorder.record()
              meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(self.updateAudioMeter(timer:)), userInfo:nil, repeats:true)
              record_btn_ref.setTitle("Stop", for: .normal)
              play_btn_ref.isEnabled = false
              isRecording = true
          }
      }
      
      func updateAudioMeter(timer: Timer)
      {
          if audioRecorder.isRecording
          {
              let hr = Int((audioRecorder.currentTime / 60) / 60)
              let min = Int(audioRecorder.currentTime / 60)
              let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60))
              let totalTimeString = String(format: "%02d:%02d:%02d", hr, min, sec)
              recordingTimeLabel.text = totalTimeString
              audioRecorder.updateMeters()
          }
      }
      
      func finishAudioRecording(success: Bool)
      {
          if success
          {
              audioRecorder.stop()
              audioRecorder = nil
              meterTimer.invalidate()
              print("recorded successfully.")
          }
          else
          {
              display_alert(msg_title: "Error", msg_desc: "Recording failed.", action_title: "OK")
          }
      }
      
    • 播放录音

      func prepare_play()
      {
          do
          {
              audioPlayer = try AVAudioPlayer(contentsOf: getFileUrl())
              audioPlayer.delegate = self
              audioPlayer.prepareToPlay()
          }
          catch{
              print("Error")
          }
      }
      
      @IBAction func play_recording(_ sender: Any)
      {
          if(isPlaying)
          {
              audioPlayer.stop()
              record_btn_ref.isEnabled = true
              play_btn_ref.setTitle("Play", for: .normal)
              isPlaying = false
          }
          else
          {
              if FileManager.default.fileExists(atPath: getFileUrl().path)
              {
                  record_btn_ref.isEnabled = false
                  play_btn_ref.setTitle("pause", for: .normal)
                  prepare_play()
                  audioPlayer.play()
                  isPlaying = true
              }
              else
              {
                  display_alert(msg_title: "Error", msg_desc: "Audio file is missing.", action_title: "OK")
              }
          }
      }
      
    • 录制完成时启用播放按钮,播放完成时启用录制按钮

      func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool)
      {
          if !flag
          {
              finishAudioRecording(success: false)
          }
          play_btn_ref.isEnabled = true
      }
      
      func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
      {
          record_btn_ref.isEnabled = true
      }
      
    • 显示警报的泛化函数

      func display_alert(msg_title : String , msg_desc : String ,action_title : String)
      {
          let ac = UIAlertController(title: msg_title, message: msg_desc, preferredStyle: .alert)
          ac.addAction(UIAlertAction(title: action_title, style: .default)
          {
              (result : UIAlertAction) -> Void in
          _ = self.navigationController?.popViewController(animated: true)
          })
          present(ac, animated: true)
      }
      

    【讨论】:

    • 您可以为此添加暂停和继续功能吗?
    • @Mamta 只要我有时间我一定会去做的。
    • 欢迎@Raja,请记住接受我的回答,快乐编码!
    • @Mili,是的。只有问题的所有者才能接受您的回答。虽然我做了向上你的回应。再次感谢。
    • @MiliShah wow Mili.. 解释得很好......!您何时实现 Mamta 要求的功能?等待它。从我这边投票。
    【解决方案2】:

    这是代码。您可以轻松录制。将此代码写入IBAction。它将通过名称recordTest.caf将录制保存在Documents

    //declare instance variable 
    var audioRecorder:AVAudioRecorder!
    func record(){        
        var audioSession:AVAudioSession = AVAudioSession.sharedInstance()
        audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
        audioSession.setActive(true, error: nil)
    
        var documents: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory,  NSSearchPathDomainMask.UserDomainMask, true)[0]
        var str =  documents.stringByAppendingPathComponent("recordTest.caf")
        var url = NSURL.fileURLWithPath(str as String)
    
        var recordSettings = [AVFormatIDKey:kAudioFormatAppleIMA4,
            AVSampleRateKey:44100.0,
            AVNumberOfChannelsKey:2,AVEncoderBitRateKey:12800,
            AVLinearPCMBitDepthKey:16,
            AVEncoderAudioQualityKey:AVAudioQuality.Max.rawValue]
    
        println("url : \(url)")
        var error: NSError?
    
        audioRecorder = AVAudioRecorder(URL:url, settings: recordSettings, error: &error)
        if let e = error {
            println(e.localizedDescription)
        } else {
            audioRecorder.record()
        }        
    }
    

    【讨论】:

    • documents 应该是(documents as NSString).append...(var str 声明代码)
    【解决方案3】:

    @codester 答案的 Swift2 版本。

    func record() {
        //init
        let audioSession:AVAudioSession = AVAudioSession.sharedInstance()
    
        //ask for permission
        if (audioSession.respondsToSelector("requestRecordPermission:")) {
            AVAudioSession.sharedInstance().requestRecordPermission({(granted: Bool)-> Void in
                if granted {
                    print("granted")
    
                    //set category and activate recorder session
                    try! audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
                    try! audioSession.setActive(true)
    
    
                    //get documnets directory
                    let documentsDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
                    let fullPath = documentsDirectory.stringByAppendingPathComponent("voiceRecording.caf")
                    let url = NSURL.fileURLWithPath(fullPath)
    
                    //create AnyObject of settings
                    let settings: [String : AnyObject] = [
                        AVFormatIDKey:Int(kAudioFormatAppleIMA4), //Int required in Swift2
                        AVSampleRateKey:44100.0,
                        AVNumberOfChannelsKey:2,
                        AVEncoderBitRateKey:12800,
                        AVLinearPCMBitDepthKey:16,
                        AVEncoderAudioQualityKey:AVAudioQuality.Max.rawValue
                    ]
    
                    //record
                    try! self.audioRecorder = AVAudioRecorder(URL: url, settings: settings)
    
                } else{
                    print("not granted")
                }
            })
        }
    
    }
    

    【讨论】:

    • 嘿 - 测试此代码后,我在 fullPath 行上得到“stringByAppendingPathComponent 不可用,请尝试 URLByAppendingPathComponent”,但这不起作用,因为 fullPath 的类型没有这样的方法。
    • @LiamShalon 你确定你正确设置了documentsDirectory var吗?如果设置不正确,则stringByAppendingPathComponent 将不存在。
    • Apple 推出 swift 2.0 时,一些字符串扩展名消失了。检查此链接以手动创建扩展:forums.developer.apple.com/thread/13580
    • 嘿,我认为你应该在try! self.audioRecorder = AVAudioRecorder(URL: url, settings: settings) 之后添加self.audioRecorder.record()。而且,documentsDirectory 不是字符串,所以你应该把(documentsDirectory as NSString).stringby...
    【解决方案4】:

    除了以前的答案之外,我尝试让它在 Xcode 7.2 上运行,之后我听不到任何声音,当我通过电子邮件发送文件时也听不到任何声音。没有警告或异常。 所以我将设置更改为以下内容并存储为 .m4a 文件。

    let recordSettings = [AVSampleRateKey : NSNumber(float: Float(44100.0)),
        AVFormatIDKey : NSNumber(int: Int32(kAudioFormatMPEG4AAC)),
        AVNumberOfChannelsKey : NSNumber(int: 1),
        AVEncoderAudioQualityKey : NSNumber(int: Int32(AVAudioQuality.Medium.rawValue))]
    

    之后我可以听声音了。 为了保存文件,我在 viewDidLoad 上添加了这个来初始化记录器:

    let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
            try audioRecorder = AVAudioRecorder(URL: self.directoryURL()!,
                settings: recordSettings)
            audioRecorder.prepareToRecord()
        } catch {
        }
    

    对于创建目录:

    func directoryURL() -> NSURL? {
        let fileManager = NSFileManager.defaultManager()
        let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
        let documentDirectory = urls[0] as NSURL
        let soundURL = documentDirectory.URLByAppendingPathComponent("sound.m4a")
        return soundURL 
    }
    

    我还添加了用于开始录制、停止和播放之后的操作

    @IBAction func doRecordAction(sender: AnyObject) {
        if !audioRecorder.recording {
            let audioSession = AVAudioSession.sharedInstance()
            do {
                try audioSession.setActive(true)
                audioRecorder.record()
            } catch {
            }
       }
    }
    @IBAction func doStopRecordingAction(sender: AnyObject) {
        audioRecorder.stop()
        let audioSession = AVAudioSession.sharedInstance()
    
        do {
            try audioSession.setActive(false)
        } catch {
        }
    }
    
    @IBAction func doPlayAction(sender: AnyObject) {
        if (!audioRecorder.recording){
            do {
                try audioPlayer = AVAudioPlayer(contentsOfURL: audioRecorder.url)
                audioPlayer.play()
            } catch {
            }
        }
    }
    

    【讨论】:

    • 对于 swift 2 和 Xcode 7.2,这应该是正确的答案。谢谢。
    • 以及,如何通过电子邮件发送文件?
    【解决方案5】:

    Here 录音机,使用 Swift 4.2 编写的简单界面。

    final class AudioRecorderImpl: NSObject {
      private let session = AVAudioSession.sharedInstance()
      private var player: AVAudioPlayer?
      private var recorder: AVAudioRecorder?
      private lazy var permissionGranted = false
      private lazy var isRecording = false
      private lazy var isPlaying = false
      private var fileURL: URL?
      private let settings = [
        AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
        AVSampleRateKey: 44100,
        AVNumberOfChannelsKey: 2,
        AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue
      ]
    
      override init() {
        fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("note.m4a")
      }
    
      func record(to url: URL?) {
        guard permissionGranted,
          let url = url ?? fileURL else { return }
    
        setupRecorder(url: url)
    
        if isRecording {
          stopRecording()
        }
    
        isRecording = true
        recorder?.record()
      }
    
      func stopRecording() {
        isRecording = false
        recorder?.stop()
        try? session.setActive(false)
      }
    
      func play(from url: URL?) {
        guard let url = url ?? fileURL else { return }
    
        setupPlayer(url: url)
    
        if isRecording {
          stopRecording()
        }
    
        if isPlaying {
          stopPlaying()
        }
    
        if FileManager.default.fileExists(atPath: url.path) {
          isPlaying = true
          setupPlayer(url: url)
          player?.play()
        }
      }
    
      func stopPlaying() {
        player?.stop()
      }
    
      func pause() {
        player?.pause()
      }
    
      func resume() {
        if player?.isPlaying == false {
          player?.play()
        }
      }
    
      func checkPermission(completion: ((Bool) -> Void)?) {
        func assignAndInvokeCallback(_ granted: Bool) {
          self.permissionGranted = granted
          completion?(granted)
        }
    
        switch session.recordPermission {
        case .granted:
          assignAndInvokeCallback(true)
    
        case .denied:
          assignAndInvokeCallback(false)
    
        case .undetermined:
          session.requestRecordPermission(assignAndInvokeCallback)
        }
      }
    }
    
    extension AudioRecorderImpl: AVAudioRecorderDelegate, AVAudioPlayerDelegate {
    
    }
    
    private extension AudioRecorderImpl {
      func setupRecorder(url: URL) {
        guard
          permissionGranted else { return }
        try? session.setCategory(.playback, mode: .default)
        try? session.setActive(true)
        let settings = [
          AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
          AVSampleRateKey: 44100,
          AVNumberOfChannelsKey: 2,
          AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
        ]
        recorder = try? AVAudioRecorder(url: url, settings: settings)
        recorder?.delegate = self
        recorder?.isMeteringEnabled = true
        recorder?.prepareToRecord()
      }
    
      func setupPlayer(url: URL) {
        player = try? AVAudioPlayer(contentsOf: url)
        player?.delegate = self
        player?.prepareToPlay()
      }
    }
    

    【讨论】:

      【解决方案6】:

      使用 Swift 4 编写类文件中的代码

      班级是AGAudioRecorder

      代码是

      class AudioRecordViewController: UIViewController {
      
          @IBOutlet weak var recodeBtn: UIButton!
          @IBOutlet weak var playBtn: UIButton!
      
          var state: AGAudioRecorderState = .Ready
      
          var recorder: AGAudioRecorder = AGAudioRecorder(withFileName: "TempFile")
      
          override func viewDidLoad() {
              super.viewDidLoad()
      
              recodeBtn.setTitle("Recode", for: .normal)
              playBtn.setTitle("Play", for: .normal)
              recorder.delegate = self
          }
      
          override func didReceiveMemoryWarning() {
              super.didReceiveMemoryWarning()
          }
      
          @IBAction func recode(_ sender: UIButton) {
              recorder.doRecord()
          }
      
          @IBAction func play(_ sender: UIButton) {
              recorder.doPlay()
          }
      }
      
      extension AudioRecordViewController: AGAudioRecorderDelegate {
          func agAudioRecorder(_ recorder: AGAudioRecorder, withStates state: AGAudioRecorderState) {
              switch state {
              case .error(let e): debugPrint(e)
              case .Failed(let s): debugPrint(s)
      
              case .Finish:
                  recodeBtn.setTitle("Recode", for: .normal)
      
              case .Recording:
                  recodeBtn.setTitle("Recoding Finished", for: .normal)
      
              case .Pause:
                  playBtn.setTitle("Pause", for: .normal)
      
              case .Play:
                  playBtn.setTitle("Play", for: .normal)
      
              case .Ready:
                  recodeBtn.setTitle("Recode", for: .normal)
                  playBtn.setTitle("Play", for: .normal)
                  refreshBtn.setTitle("Refresh", for: .normal)
              }
              debugPrint(state)
          }
      
          func agAudioRecorder(_ recorder: AGAudioRecorder, currentTime timeInterval: TimeInterval, formattedString: String) {
              debugPrint(formattedString)
          }
      }
      

      【讨论】:

      • 可以将您的代码用于商业应用程序吗? (或者我可以通过查看您的代码来创建自己的)
      • 现在我也在处理这段代码,并尽快上传到 git 存储库。这个包装类为我的应用程序和其他人使用创建,所以我上传了公开。如果你创造得很好,那就分享给我,我也会帮助你编写代码
      【解决方案7】:

      对于 Swift 5

       func setup_recorder()
      {
          if isAudioRecordingGranted
          {
              let session = AVAudioSession.sharedInstance()
              do
              {
                  try session.setCategory(.playAndRecord, mode: .default)
                  try session.setActive(true)
                  let settings = [
                      AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                      AVSampleRateKey: 44100,
                      AVNumberOfChannelsKey: 2,
                      AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue
                  ]
                  audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings)
                  audioRecorder.delegate = self
                  audioRecorder.isMeteringEnabled = true
                  audioRecorder.prepareToRecord()
              }
              catch let error {
                  display_alert(msg_title: "Error", msg_desc: error.localizedDescription, action_title: "OK")
              }
          }
          else
          {
              display_alert(msg_title: "Error", msg_desc: "Don't have access to use your microphone.", action_title: "OK")
          }
      

      【讨论】:

        【解决方案8】:

        Swift 3 代码版:完整的录音解决方案!

        import UIKit
        import AVFoundation
        
        class ViewController: UIViewController, AVAudioRecorderDelegate {
        
            //Outlets
            @IBOutlet weak var recordingTimeLabel: UILabel!
        
            //Variables
            var audioRecorder: AVAudioRecorder!
            var meterTimer:Timer!
            var isAudioRecordingGranted: Bool!
        
        
            override func viewDidLoad() {
                super.viewDidLoad()
        
                switch AVAudioSession.sharedInstance().recordPermission() {
                case AVAudioSessionRecordPermission.granted:
                    isAudioRecordingGranted = true
                    break
                case AVAudioSessionRecordPermission.denied:
                    isAudioRecordingGranted = false
                    break
                case AVAudioSessionRecordPermission.undetermined:
                    AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in
                        DispatchQueue.main.async {
                            if allowed {
                                self.isAudioRecordingGranted = true
                            } else {
                                self.isAudioRecordingGranted = false
                            }
                        }
                    }
                    break
                default:
                    break
                }
            }
        
            override func didReceiveMemoryWarning() {
                super.didReceiveMemoryWarning()
        
                audioRecorder = nil
            }
        
            //MARK:- Audio recorder buttons action.
            @IBAction func audioRecorderAction(_ sender: UIButton) {
        
                if isAudioRecordingGranted {
        
                    //Create the session.
                    let session = AVAudioSession.sharedInstance()
        
                    do {
                        //Configure the session for recording and playback.
                        try session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)
                        try session.setActive(true)
                        //Set up a high-quality recording session.
                        let settings = [
                            AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                            AVSampleRateKey: 44100,
                            AVNumberOfChannelsKey: 2,
                            AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
                        ]
                        //Create audio file name URL
                        let audioFilename = getDocumentsDirectory().appendingPathComponent("audioRecording.m4a")
                        //Create the audio recording, and assign ourselves as the delegate
                        audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
                        audioRecorder.delegate = self
                        audioRecorder.isMeteringEnabled = true
                        audioRecorder.record()
                        meterTimer = Timer.scheduledTimer(timeInterval: 0.1, target:self, selector:#selector(self.updateAudioMeter(timer:)), userInfo:nil, repeats:true)
                    }
                    catch let error {
                        print("Error for start audio recording: \(error.localizedDescription)")
                    }
                }
            }
        
            @IBAction func stopAudioRecordingAction(_ sender: UIButton) {
        
                finishAudioRecording(success: true)
        
            }
        
            func finishAudioRecording(success: Bool) {
        
                audioRecorder.stop()
                audioRecorder = nil
                meterTimer.invalidate()
        
                if success {
                    print("Recording finished successfully.")
                } else {
                    print("Recording failed :(")
                }
            }
        
            func updateAudioMeter(timer: Timer) {
        
                if audioRecorder.isRecording {
                    let hr = Int((audioRecorder.currentTime / 60) / 60)
                    let min = Int(audioRecorder.currentTime / 60)
                    let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60))
                    let totalTimeString = String(format: "%02d:%02d:%02d", hr, min, sec)
                    recordingTimeLabel.text = totalTimeString
                    audioRecorder.updateMeters()
                }
            }
        
            func getDocumentsDirectory() -> URL {
        
                let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
                let documentsDirectory = paths[0]
                return documentsDirectory
            }
        
            //MARK:- Audio recoder delegate methods
            func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
        
                if !flag {
                    finishAudioRecording(success: false)
                }
            }
        }
        

        【讨论】:

        • 您可以为此添加暂停和继续功能吗?
        • 嗨@Mamta,你能提交一个新问题吗?所以我可以回答它,因为我需要使用不同的委托来播放具有播放、暂停和停止功能的音频。我希望你能理解。此页面仅用于录音。请在此处提供问题的 URL。谢谢-mriaz0011
        • @mriaz0011:这段代码是录制直播音频还是输出音频?
        猜你喜欢
        • 1970-01-01
        • 2015-03-05
        • 2016-03-25
        • 2023-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-18
        • 2020-03-23
        相关资源
        最近更新 更多