分块解密媒体文件并通过 AVPlayer 播放

Posted

技术标签:

【中文标题】分块解密媒体文件并通过 AVPlayer 播放【英文标题】:Decrypt Media Files in chunks and play via AVPlayer 【发布时间】:2016-05-03 21:28:44 【问题描述】:

我有一个mp4 video file,我是encrypting 可以保存,decrypting 可以通过 AVPlayer 播放。 Using CRYPTOSWIFT Library for encrypting/decrypting

当我一次解密整个文件但我的文件非常大并且占用 100% 的 CPU 使用率和大量内存时,它工作正常。所以,我需要分块解密加密文件。 我试图以块的形式解密文件,但它没有播放视频,因为 AVPlayer 无法识别解密的块数据,可能在加密文件时数据没有按顺序存储。我尝试过chacha20, AES, AES.CTR & AES.CBC 协议来加密和解密文件,但无济于事。

extension PlayerController: AVAssetResourceLoaderDelegate 

   func resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool 
      let request = loadingRequest.request
      guard let path = request.URL?.path where request.URL?.scheme == Constants.customVideoScheme else  return true 
      if let contentRequest = loadingRequest.contentInformationRequest 
         do 
            let fileAttributes = try NSFileManager.defaultManager().attributesOfItemAtPath(path)
            if let fileSizeNumber = fileAttributes[NSFileSize] 
               contentRequest.contentLength = fileSizeNumber.longLongValue
            
          catch  

         if fileHandle == nil 
            fileHandle = NSFileHandle(forReadingAtPath: (request.URL?.path)!)!
         
         contentRequest.contentType = "video/mp4"
         contentRequest.byteRangeAccessSupported = true
      

      if let data = decryptData(loadingRequest, path: path), dataRequest = loadingRequest.dataRequest 
         dataRequest.respondWithData(data)
         loadingRequest.finishLoading()
         return true
      
      return true
   

   func decryptData(loadingRequest: AVAssetResourceLoadingRequest, path: String) -> NSData? 
      print("Current OFFSET: \(loadingRequest.dataRequest?.currentOffset)")
      print("requested OFFSET: \(loadingRequest.dataRequest?.requestedOffset)")
      print("Current Length: \(loadingRequest.dataRequest?.requestedLength)")
      if loadingRequest.contentInformationRequest != nil 
         var data = fileHandle!.readDataOfLength((loadingRequest.dataRequest?.requestedLength)!)
         fileHandle!.seekToFileOffset(0)
         data = decodeVideoData(data)!
         return data
       else 
         fileHandle?.seekToFileOffset(UInt64((loadingRequest.dataRequest?.currentOffset)!))
         let data = fileHandle!.readDataOfLength((loadingRequest.dataRequest?.requestedLength)!)
// let data = fileHandle!.readDataOfLength(length!) ** When I use this its not playing video but play fine when try with requestedLength **
         return decodeVideoData(data)
      
   

解码 nsdata 的代码:

 func decodeVideoData(data: NSData) -> NSData? 
      if let cha = ChaCha20(key: Constants.Encryption.SecretKey, iv: Constants.Encryption.IvKey) 
         let decrypted: NSData = try! data.decrypt(cha)
         return decrypted
      
      return nil
   

我需要有关此问题的帮助,请指导我找到实现此目标的正确方法。

【问题讨论】:

你喜欢逐块播放吗?在这种情况下,请查看“HTTP 直播流”。你喜欢解密整个“媒体文件”吗?那样的话,有什么问题呢? 我试图一次解密一个块,但 AVPlayer 没有播放视频,因为我认为加密文件可能没有按顺序写入数据?我如何使用http流播放本地文件你能详细说明吗? 它不起作用,因为您的 decodeVideoData 每次都使用相同的密钥和随机数初始化解码器(它一次又一次地启动“一次性垫”,无论您从哪个文件偏移量读取数据)。一般来说,在编码文件中搜索“按需”解码是不可能的。 如果我从 loadingRequest 传递requestedLength,它就可以工作。 好吧,也许我做错了,你能详细说明我应该怎么做才能让它工作吗?对代码进行小的重写会很棒。谢谢! 【参考方案1】:

如需更深入和更完整的 CommonCrypto 包装器,请查看my CommonCrypto wrapper。我已经为这个答案提取了点点滴滴。

首先,我们需要定义一些将进行加密/解密的函数。我现在假设您使用 AES(256) CBC 和 PKCS#7 填充。总结下面的 sn-p:我们有一个更新函数,可以重复调用来消耗块。还有一个 final 函数可以包装任何剩余部分(通常处理填充)。

import CommonCrypto
import Foundation

enum CryptoError: Error 
    case generic(CCCryptorStatus)


func getOutputLength(_ reference: CCCryptorRef?, inputLength: Int, final: Bool) -> Int 
    CCCryptorGetOutputLength(reference, inputLength, final)


func update(_ reference: CCCryptorRef?, data: Data) throws -> Data 
    var output = [UInt8](repeating: 0, count: getOutputLength(reference, inputLength: data.count, final: false))
    let status = data.withUnsafeBytes  dataPointer -> CCCryptorStatus in
        CCCryptorUpdate(reference, dataPointer.baseAddress, data.count, &output, output.count, nil)
    
    guard status == kCCSuccess else 
        throw CryptoError.generic(status)
    
    return Data(output)


func final(_ reference: CCCryptorRef?) throws -> Data 
    var output = [UInt8](repeating: 0, count: getOutputLength(reference, inputLength: 0, final: true))
    var moved = 0
    let status = CCCryptorFinal(reference, &output, output.count, &moved)
    guard status == kCCSuccess else 
        throw CryptoError.generic(status)
    
    output.removeSubrange(moved...)
    return Data(output)

接下来,为了演示,加密。

let key = Data(repeating: 0x0a, count: kCCKeySizeAES256)
let iv = Data(repeating: 0, count: kCCBlockSizeAES128)
let bigFile = (0 ..< 0xffff).map  _ in
    return Data(repeating: UInt8.random(in: 0 ... UInt8.max), count: kCCBlockSizeAES128)
.reduce(Data(), +)

var encryptor: CCCryptorRef?
CCCryptorCreate(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), Array(key), key.count, Array(iv), &encryptor)

do 
    let ciphertext = try update(encryptor, data: bigFile) + final(encryptor)
    print(ciphertext)    // 1048576 bytes
 catch 
    print(error)

在我看来这是一个相当大的文件。现在解密,将以类似的方式完成。

var decryptor: CCCryptorRef?
CCCryptorCreate(CCOperation(kCCDecrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), Array(key), key.count, Array(iv), &decryptor)

do 
    var plaintext = Data()
    for i in 0 ..< 0xffff 
        plaintext += try update(decryptor, data: ciphertext[i * kCCBlockSizeAES128 ..< i * kCCBlockSizeAES128 + kCCBlockSizeAES128])
    
    plaintext += try final(decryptor)
    
    print(plaintext == bigFile, plaintext)    // true 1048560 bytes
 catch 
    print(error)

加密器可以针对不同的模式进行更改,完成后也应该释放,我不太确定 update 函数上的任意输出会如何表现,但这应该足以让您了解如何使用CommonCrypto 来完成。

【讨论】:

以上是关于分块解密媒体文件并通过 AVPlayer 播放的主要内容,如果未能解决你的问题,请参考以下文章

使用 AVPLayer 实现媒体播放器以在 Swift 4 的模态视图控制器的弹出视图中播放来自远程 url 的音频

如何禁用暂停/播放通过 Airplay 连接到 Apple TV 的 AVPlayer 的功能

AVPlayer音频和视频播放

iOS音频播放

AVPlayer 不在后台转发流媒体轨道

swift中的AVPlayer状态监听器