• iOS 视频流要怎么播放

    2016-05-19 08:55:17
    这种格式的视频流要怎么在线播放?急急急 http://kzy2015.wicp.net:4267/zh/api/webapi/DownScenicAreaFile/7?filetype=1&ScenicAreaId=4&fuid=19&fsbm=XaVeMGU84dSddP6gyhhO
  • iOS视频开发经验

    2016-04-18 13:16:26
    iOS视频开发经验 手机比PC的优势除了便携外,我认为最重要的就是可以快速方便的创作多媒体作品。照片分享,语音输入,视频录制,地理位置。一个成功的手机APP从产品形态上都有这其中的一项或多项,比如instagram,...

    iOS视频开发经验

    手机比PC的优势除了便携外,我认为最重要的就是可以快速方便的创作多媒体作品。照片分享,语音输入,视频录制,地理位置。一个成功的手机APP从产品形态上都有这其中的一项或多项,比如instagram,微信。如果把Web2.0的交互体验照搬到手机上就是死路一条。 当智能手机遇上视频就像潘金莲遇上西门庆,各取所需一拍即合,想不发生点事情都难。他们的结晶就是微视频。微视频可以说把手机的视频录制和碎片时间两个特点发挥到了极致,视频相关的APP现在无温不火的原因我认为跟坑爹的运营商有关。虽然现在移动网络流量小速度慢,但是不妨碍我们先把技术积累做起来。

    这篇文章主要介绍本人在iOS视频开发中的一点经验。

    视频实质:

    纯粹的视频(不包括音频)实质上就是一组帧图片,经过视频编码成为视频(video)文件再把音频(audio)文件有些还有字幕文件组装在一起成为我们看到的视频(movie)文件。1秒内出现的图片数就是帧率,图片间隔越小画面就越流畅,所以帧率越高效果就越好,需要的存储空间也就越多。

    视频编码:

    因为不进行编码的视频数据量非常大,会造成存储和传输上的困难,所以视频文件都需要在录制完成后进行编码。视频编码主要从两个维度压缩数据。

    • 1、单张图像某一区域相邻像素相似,比如一片红色只记录红色色值和区域,不用记录这个区域的每一个像素点。
    • 2、相邻图像之间内容相似,因为相邻两帧要制造连续的效果,所以两帧之间的内容一般非常接近。目前主流的视频编码技术都是用图像编码方法对第一帧进行编码,然后用某种方式描述接下来的帧相对于附近的帧有什么区别。

    视频格式:

    MP4、MOV、AVI、RMVB这些播放格式其实都是封装格式,除了RMVB比较特殊外,其他格式内封装的视频编码格式都是H264,H264以高压缩率闻名于世,压缩效率比MEPG-2提升一倍多,但是世上没有两全其美的事,H264的解码难度提高了3倍多。

    视频码率:

    视频文件的大小除以是视频的时长定义为码率。

    码率和分辨率跟视频质量的关系:

    • 码率可以理解为取样率,单位时间内取样率越大,精度就越高,同时体积也越大。
    • 当视频没有经过编码时,如果分辨率越高,那么视频图像的细节越清晰。
    • 但如果视频经过编码,被限制在一定码率内,编码器就必须舍弃掉一部分细节。
    • 所以分辨率和码率都同清晰度有关。

    软解码和硬解码:

    对H264的视频解码给CPU造成了很大负担,所以手机工程师把这部分工作交给了更善于进行处理简单工作但是数据量较大的GPU。

    • GPU解码就是所谓的硬解码
    • CPU解码就是软解码。
    • iOS提供的播放器类使用的是硬解码,所以视频播放对CPU不会有很大的压力,但是支持的播放格式比较单一,一般就是MP4、MOV、M4V这几个。

    HTTP Live Streaming

    HLS简介

    HTTP Live Streaming(缩写是 HLS)是一个由苹果公司提出的基于HTTP的流媒体网络传输协议。它的工作原理是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些。
    当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。支持的视频流编码为H.264。我们在视频网站上看到的M3U8后缀的播放链接就是使用HLS协议的视频。

    HLS优点
    • 1、看完一段缓存一段,防止只看一段视频但是把整个视频文件都缓存下来的用户,减少服务器压力和节省流量。
    • 2、根据用户网速切换不同的码率,兼顾流程性和清晰度。
    HLS支持情况
    • iOS 3.0及之后的版本
    • Android 3.0及之后的版本
    • HTML5。

    终端播放格式的选取

    • Android由于3.0之后才支持HLS,所以Android2.3只能用MP4。
    • Android3.0及之后支持HLS。可以用m3u8、mp4格式
    • iOS支持HLS,但不支持flash。可以用m3u8、mp4格式
    • 支持HTML5的浏览器 可以用m3u8。
    • 不支持HTML5的浏览器只能用flash播放swf。

    由于以上原因,目前无法实现一个播放地址在所有的平台都通用。

    iOS视频播放:

    iOS提供MPMoviePlayerController类进行播放,支持流媒体和文件播放。视频内容会渲染到他的View上,可以放在你想放的任何地方,用起来比较方便。这个类设计上不合理的是视频播放状态和视频加载状态都是通过Notification通知的,而不是通过block或者delegate。

    iOS视频录制:

    同拍照一样视频录制功能有两种实现方式

    • 1、UIImagePickerViewController
    • 2、AVFoundation。

    这里只讨论AVFoundation框架,这个框架是苹果提供的底层多媒体框架,用于音视频采集、音视频解码、视频编辑等,多媒体基本上都依赖AVFoundation框架。

    视频录制和拍照需要做的工作差不多,主要有以下5步:

    • 1、创建会话AVCaptureSession,用于控制input到output的流向。
    • 2、获取设备AVCaptureDevice,摄像头用于视频采集,话筒用于音频采集。
    • 3、创建输入设备AVCaptureDeviceInput,将设备绑定到input口中,并添加到session上
    • 4、创建输出AVCaptureOutput,可以输出到文件和屏幕上。 AVCaptureMovieFileOutput 输出一个电影文件 AVCaptureVideoDataOutput 输出处理视频帧,用于显示正在录制的视频 AVCaptureAudioDataOutput 输出音频数据
    • 5、音视频合成到一个文件中

    iOS对视频实时处理:

    如果需要对视频进行实时处理(当然需要否则看不到正在录制的内容),则需要直接对相机缓冲区(camera buffer)中的视频流进行处理。

    • 1、定义一个视频数据输出(AVCaptureVideoDataOutput), 并将其添加到session上。
    • 2、设置接受的controller作为视频数据输出缓冲区(sample buffer)的代理。
    • 3、实现代理方法
      -(void)captureOutput:(AVCaptureOutput )captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection )connection
      当数据缓冲区(data buffer)一有数据时,AVFoundation就调用该方法。在该代理方法中,我们可以获取视频帧、处理视频帧、显示视频帧。实时滤镜就是在这里进行处理的。在这个方法中将缓冲区中的视频数据(就是帧图片)输出到要显示的layer上。
    展开全文
  • 使用HLS,您可以以不同的比特率提供多个媒体,并且您的播放客户端会随着网络带宽的变化动态选择适当的。这可确保您始终根据用户当前的网络状况提供最优质的内容。本章介绍如何在播放应用中利用HLS的独特功能。 ...

    HTTP Live Streaming (HLS))是向播放应用提供媒体的理想方式。使用HLS,您可以以不同的比特率提供多个媒体流,并且您的播放客户端会随着网络带宽的变化动态选择适当的流。这可确保您始终根据用户当前的网络状况提供最优质的内容。本章介绍如何在播放应用中利用HLS的独特功能。

    从iOS 10开始,您可以使用AVFoundation将HTTP Live Streaming资源下载到iOS设备。这项新功能允许用户在可以访问快速,可靠的网络时在其设备上下载和存储HLS电影,并在以后无需网络连接即可观看。通过引入此功能,HLS可以最小化不一致的网络可用性对用户体验的影响。

    使用AVAssetDownloadURLSession来实现资源下载功能,他是NSURLSession的子类,主要用来创建和执行资源下载任务。

    func setupAssetDownload() {
        // Create new background session configuration.
        configuration = URLSessionConfiguration.background(withIdentifier: downloadIdentifier)
     
        // Create a new AVAssetDownloadURLSession with background configuration, delegate, and queue
        downloadSession = AVAssetDownloadURLSession(configuration: configuration,
                                                    assetDownloadDelegate: self,
                                                    delegateQueue: OperationQueue.main)
    }

     配置完URLSession以后,创建AVAssetDownloadTask实例来开始下载任务

    func setupAssetDownload() {
        ...
        // Previous AVAssetDownloadURLSession configuration
        ...
     
        let url = // HLS Asset URL
        let asset = AVURLAsset(url: url)
     
        // Create new AVAssetDownloadTask for the desired asset
        let downloadTask = downloadSession.makeAssetDownloadTask(asset: asset,
                                                                 assetTitle: assetTitle,
                                                                 assetArtworkData: nil,
                                                                 options: nil)
        // Start task and begin download
        downloadTask?.resume()
    }

     options选型可以传入一个Dictionary用来选择不同的比特和媒体选型。如果为Nil,会默认选择下载最高质量的音视频内容。由于可以在后台实现下载,当App被终止时,下次启动需要恢复上次的下载任务,此时可以通过一个上次任务在 NSURLSessionConfiguration中配置的identifier 来新创建一个NSURLSessionConfiguration对象,重新创建AVAssetDownloadURLSession对象,使用session的getTasksWithCompletionHandler:方法来获取终止的任务,来恢复下载任务。

    func restorePendingDownloads() {
        // Create session configuration with ORIGINAL download identifier
        configuration = URLSessionConfiguration.background(withIdentifier: downloadIdentifier)
     
        // Create a new AVAssetDownloadURLSession
        downloadSession = AVAssetDownloadURLSession(configuration: configuration,
                                                    assetDownloadDelegate: self,
                                                    delegateQueue: OperationQueue.main)
     
        // Grab all the pending tasks associated with the downloadSession
        downloadSession.getAllTasks { tasksArray in
            // For each task, restore the state in the app
            for task in tasksArray {
                guard let downloadTask = task as? AVAssetDownloadTask else { break }
                // Restore asset, progress indicators, state, etc...
                let asset = downloadTask.urlAsset
            }
        }
    }

     可以通过代理方法监控下载的进度:

    func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didLoad timeRange: CMTimeRange, totalTimeRangesLoaded loadedTimeRanges: [NSValue], timeRangeExpectedToLoad: CMTimeRange) {
        var percentComplete = 0.0
        // Iterate through the loaded time ranges
        for value in loadedTimeRanges {
            // Unwrap the CMTimeRange from the NSValue
            let loadedTimeRange = value.timeRangeValue
            // Calculate the percentage of the total expected asset duration
            percentComplete += loadedTimeRange.duration.seconds / timeRangeExpectedToLoad.duration.seconds
        }
        percentComplete *= 100
        // Update UI state: post notification, update KVO state, invoke callback, etc.
    }
    

    当下载完成时或下载失败了会调用session的代理方法,可以在此方法中设置文件保存位置,与NSURLSessionDownloadDelegate中URLSession:downloadTask:didFinishDownloadingToURL:方法不同的时,用户不应该修该下载资源的位置,它是在系统的控制之下的,传入的URL,代表了下载资源在磁盘上的最终位置。

    func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
        // Do not move the asset from the download location
        UserDefaults.standard.set(location.relativePath, forKey: "assetPath")
    }

    你可以更新下载的资源,例如以前服务器并不提供高清的资源或者一些 资源选项。当服务器有资源更新的时候 session的代理方法URLSession:assetDownloadTask:didResolveMediaSelection:会被调用,保存AVMediaSelection对象,用于随后的下载任务

    func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didResolve resolvedMediaSelection: AVMediaSelection) {
        // Store away for later retrieval when main asset download is complete
        // mediaSelectionMap is defined as: [AVAssetDownloadTask : AVMediaSelection]()
        mediaSelectionMap[assetDownloadTask] = resolvedMediaSelection
    }

    通过以下方法获取未在本地缓存的内容

    func nextMediaSelection(_ asset: AVURLAsset) -> (mediaSelectionGroup: AVMediaSelectionGroup?,
                                                     mediaSelectionOption: AVMediaSelectionOption?) {
     
        // If the specified asset has not associated asset cache, return nil tuple
        guard let assetCache = asset.assetCache else {
            return (nil, nil)
        }
     
        // Iterate through audible and legible characteristics to find associated groups for asset
        for characteristic in [AVMediaCharacteristicAudible, AVMediaCharacteristicLegible] {
     
            if let mediaSelectionGroup = asset.mediaSelectionGroup(forMediaCharacteristic: characteristic) {
     
                // Determine which offline media selection options exist for this asset
                let savedOptions = assetCache.mediaSelectionOptions(in: mediaSelectionGroup)
     
                // If there are still media options to download...
                if savedOptions.count < mediaSelectionGroup.options.count {
                    for option in mediaSelectionGroup.options {
                        if !savedOptions.contains(option) {
                            // This option hasn't been downloaded. Return it so it can be.
                            return (mediaSelectionGroup, option)
                        }
                    }
                }
            }
        }
        // At this point all media options have been downloaded.
        return (nil, nil)
    }

    下面代码检查并下载所有的可选媒体资源 

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
     
        guard error == nil else { return }
        guard let task = task as? AVAssetDownloadTask else { return }
     
        // Determine the next available AVMediaSelectionOption to download
        let mediaSelectionPair = nextMediaSelection(task.urlAsset)
     
        // If an undownloaded media selection option exists in the group...
        if let group = mediaSelectionPair.mediaSelectionGroup,
               option = mediaSelectionPair.mediaSelectionOption {
     
            // Exit early if no corresponding AVMediaSelection exists for the current task
            guard let originalMediaSelection = mediaSelectionMap[task] else { return }
     
            // Create a mutable copy and select the media selection option in the media selection group
            let mediaSelection = originalMediaSelection.mutableCopy() as! AVMutableMediaSelection
            mediaSelection.select(option, in: group)
     
            // Create a new download task with this media selection in its options
            let options = [AVAssetDownloadTaskMediaSelectionKey: mediaSelection]
            let task = downloadSession.makeAssetDownloadTask(asset: task.urlAsset,
                                                             assetTitle: assetTitle,
                                                             assetArtworkData: nil,
                                                             options: options)
     
            // Start media selection download
            task?.resume()
     
        } else {
            // All media selection downloads complete
        }
    }
    

    一旦开始下载,就可以开始同步播放

    func downloadAndPlayAsset(_ asset: AVURLAsset) {
        // Create new AVAssetDownloadTask for the desired asset
        // Passing a nil options value indicates the highest available bitrate should be downloaded
        let downloadTask = downloadSession.makeAssetDownloadTask(asset: asset,
                                                                 assetTitle: assetTitle,
                                                                 assetArtworkData: nil,
                                                                 options: nil)!
        // Start task
        downloadTask.resume()
     
        // Create standard playback items and begin playback
        let playerItem = AVPlayerItem(asset: downloadTask.urlAsset)
        player = AVPlayer(playerItem: playerItem)
        player.play()
    }

    也可以播放下载完成的离线资源,使用在下载完成后指定为资源位置

    func playOfflineAsset() {
        guard let assetPath = UserDefaults.standard.value(forKey: "assetPath") as? String else {
            // Present Error: No offline version of this asset available
            return
        }
        let baseURL = URL(fileURLWithPath: NSHomeDirectory())
        let assetURL = baseURL.appendingPathComponent(assetPath)
        let asset = AVURLAsset(url: assetURL)
        if let cache = asset.assetCache, cache.isPlayableOffline {
            // Set up player item and player and begin playback
        } else {
            // Present Error: No playable version of this asset exists offline
        }
    }

     删除文件:

    func deleteOfflineAsset() {
        do {
            let userDefaults = UserDefaults.standard
            if let assetPath = userDefaults.value(forKey: "assetPath") as? String {
                let baseURL = URL(fileURLWithPath: NSHomeDirectory())
                let assetURL = baseURL.appendingPathComponent(assetPath)
                try FileManager.default.removeItem(at: assetURL)
                userDefaults.removeObject(forKey: "assetPath")
            }
        } catch {
            print("An error occured deleting offline asset: \(error)")
        }
    }
    

    AVPlayerItem的accessLog and errorLog属性用来读取日志文件。 

    展开全文
  • 本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义...
  • 这段时间对视频开发进行了一些了解,在这里和大家分享一下我... 要了解iOS视频开发,首先我们从系统自带的播放器说起,一、我们可以直接播放视频,看到效果,不然搞了半天还播放不了视频,会让大家失去兴趣。


    原文地址:http://bluereader.org/article/158707893


    这段时间对视频开发进行了一些了解,在这里和大家分享一下我自己觉得学习步骤和资料,希望对那些对视频感兴趣的朋友有些帮助。

    一、iOS系统自带播放器

     

      要了解iOS视频开发,首先我们从系统自带的播放器说起,一、我们可以直接播放视频,看到效果,不然搞了半天还播放不了视频,会让大家失去兴趣。二、其实对于很多需求来说,系统的播放器就能够胜任。简单介绍下

    1.MPMoviePlayerController

    在iOS中播放视频可以使用MPMoviePlayerController类来完成,具备一般的播放器控制功能,例如播放、暂停、停止等。但是MPMediaPlayerController自身并不是一个完整的视图控制器,如果要在UI中展示视频需要将view属性添加到界面中

    2.MPMoviePlayerViewController

    MPMoviePlayerController继承于UIViewController,默认是全屏模式展示、弹出后自动播放、作为模态窗口展示时如果点击“Done”按钮会自动退出模态窗口等

    3.AVPlayer

    MPMoviePlayerController足够强大和复。自定义播放器的样式,使用MPMoviePlayerController就不合适了,只能用AVPlayer.

    AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。

    4.AVFoundation

    深入学习音视频播放,需要对AVFoundation框架进行深入学习

     

    但是无论是MPMoviePlayerController还是AVPlayer支持的视频编码格式很有限:H.264、MPEG-4,扩展名(压缩格式):.mp4、.mov、.m4v、.m2v、.3gp、.3g2等。

     

    二、使用第三方Kxmovie

     

    1.配置Kxmovie

    git clone https://github.com/kolyvan/kxmovie.git 

    cd kxmovie

    git submodule update --init 

    sudo rake //会出现错误,见错误1

     

    2.遇到的问题及解决办法:

      a.执行sudo rake 时abort

    在kxmovie目录下 

    执行vim Rakefile 

    找到SDK_VERSION、XCODE_PATH两行,改为下面 

    SDK_VERSION='9.2' 

    XCODE_PATH='/Applications/Xcode.app/Contents/Developer/Platforms' 

     

    解释:SDK_VERSION=‘9.2’中9.2是你现在的sdk版本可以执行 

    cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/developer/SDKs/

    去查看目前的sdk的版本,改为对应的版本

     

    b.Undefined symbols for architecture x86_64

    Kxmovie应该是不支持64为模拟器上运行,不可以在iPhone5s以上模拟器上运行。可以在5上运行

     

    三、视频基础知识介绍

     

      1.视频播放器原理

    • 通过流媒体协议如RTSP+RTP、HTTP、MMS等下载的数据通过解协议获得封装格式数据,何为封装格式的数据。如AVI、MP4、FLV等;
    • 对于封装格式的数据进行解封装,提取视频流、音频流、字幕流进行分离待下一步准备处理,
    • 分离后获得音视频文件编码文件(音视频文件过大需要进行压缩进行传输,即编码),常见的编码如H.264编码的视频码流和AAC编码的音频码流。压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV420P,RGB等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如PCM数据。
    • 视音频同步,视频流、音频流、字幕流等进行同步播放。

    2.流媒体传输协议

      一般点播采用HTTP ,而直播的话,大部分还是采用RTMP或者私有协议,原因是延时会比较小,RTMP本身也是为了直播设计的

    • RSVP:资源预留协议
    • RTP:实时传输协议
    • RTCP:实时传输控制协议
    • MMS:微软流媒体服务协议
    • RTSP:实时流传输协议
    • MIME:多目因特网电子邮件扩展协议
    • RTMP(RTMPE/RTMPS/RTMPT):Adobe实时消息协议簇
    • RTMFP:Adobe实施消息流协议(P2P协议)
    • HLS(Http Live Streaming)

    流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls) http://blog.csdn.net/tttyd/article/details/12032357 

    视频流传输协议RTP/RTCP/RTSP/HTTP的区别 http://blog.csdn.net/yangxt/article/details/7467457

    3.封装格式

    封装格式(也叫容器)主要作用是把视频码流和音频码流按照一定的格式存储在一个文件中。

    常见格式

    AVI:微软在90年代初创立的封装标准,是当时为对抗quicktime格式(mov)而推出的,只能支持固定CBR恒定比特率编码的声音文件。

    FLV:针对于h.263家族的格式。

    MKV:万能封装器,有良好的兼容和跨平台性、纠错性,可带 外挂字幕。

    MOV:MOV是Quicktime封装。

    MP4:主要应用于mpeg4的封装 。

    RM/RMVB:Real Video,由RealNetworks开发的应用于rmvb和rm 。

    TS/PS:PS封装只能在HDDVD原版。

    WMV:微软推出的,作为市场竞争。

    4.编码标准

    视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。

     

    视频编码标准汇总及比较 http://blog.csdn.net/leixiaohua1020/article/details/12031631

    视音频编解码技术零基础学习方法 http://blog.csdn.net/leixiaohua1020/article/details/18893769

    5.播放方式

    视频直播,是对视频源的实时的观看,不能快进等操作,注重实时性,对网络延迟要求比较高,相当于视频的广播

    视频点播,是对以往的视频源进行回放,可以执行快进后退等操作

    6.FFmpeg

    http://ffmpeg.org/doxygen/2.8/examples.html 官网介绍 

    http://blog.csdn.net/leixiaohua1020/article/details/44084321 博客地址 

    http://blog.csdn.net/beitiandijun/article/details/8280448  FFmpeg的基本概念

    多媒体视频处理工具FFmpeg有非常强大的功能包括视频采集功能、视频格式转换、视频抓图、给视频加水印等。

    FFmpeg的基本概念:

    容器(container):就是文件格式,在FFMPEG中,用来抽象文件格式的容器就是AVFormatContext; 

    数据流(stream):数据流就是我们平时看到的多媒体数据流,它包含几种基本的数据流,包括:视频流、音频流、字幕流;按照我的理解,数据流在FFMPEG中的抽象为AVStream。 

    解复用器或者说分流器(demuxer):FFMPEG将要处理的多媒体文件看成多媒体数据流,先把多媒体数据流放入容器(AVFormatContext),然后将数据流送入解复用器(demuxer),demuxer在FFMPEG中的抽象为AVInputFormat,我更愿意把demuxer称为分流器,因为demuxer就是把交错的各种基本数据流识别然后分开处理,将分开的数据流分别送到视频、音频、字幕编解码器处理。

    数据包(packet)当然分开的数据流在送往编解码器处理之前,要先放于缓存中,同时添加一些附属信息例如打上时间戳,以便后面处理,那么这个缓存空间就是数据包;由于数据流是在时间轴上交错放置,所以所有的视频、音频、字幕都被分割成一段一段的数据,这些一段段的数据从数据流中解析出来之后,就是存放在各自的packet,那么在这里要说明一下,单纯的视频数据包来说,一个视频数据包可以存放一个视频帧,对于单纯的音频帧来说,如果抽样率(sample-rate)是固定不变的,一个音频数据包可以存放几个音频帧,若是抽样率是可变的,则一个数据包就只能存放一个音频帧。

     

    四、Kxmovie源码分析简易分析

     

    整体思路是KxMovieDecoder通过视频文件或者网络地址使用FFmpeg解码,将视频文件解码为YUV或者RGB文件(图像文件)。然后KxMovieGLView呈现YUV或者RGB文件。KxAudioManager进行播放管理,例如paly,pause等,KxMovieViewController使用以上API,构建播放器界面

     1.KxMovieDecoder文件 

    KxMovieDecoder提供解码的API,在vedio解码为YUV或者RGB文件。

    从公共API入手,进行分析。以下分析只是提取了vedio的操作。

    a.打开文件,进行如下操作

     + (id) movieDecoderWithContentPath: (NSString *) path  error: (NSError **) perror

    1. 打开网络流的话,前面要加上函数avformat_network_init()。
    2. AVFormatContext:统领全局的基本结构体。主要用于处理封装格式(FLV/MKV/RMVB等)。AVFormatContext初始化方法avformat_alloc_context()
    3. 打开输入流,四个参数分别是ps:AVFormatContext对象的地址,filename:输入流的文件名,fmt:如果非空,这个参数强制一个特定的输入格式。否则自动适应格式。int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);
    4. 读取数据包获取流媒体文件的信息,每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据。int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
    5. 找到合适的解码器,
    6. AVCodecContext *codecCtx = _formatCtx->streams[videoStream]->codec;AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
    7. Initialize the AVCodecContext to use the given AVCodec.  return zero on success, a negative value on error avcodec_open2(codecCtx, codec, NULL);

    b. - (BOOL) openFile: (NSString *) path error: (NSError **) perror;

    与方法a相比,方法a只是比此方法多了初始化方法 KxMovieDecoder *mp = [[KxMovieDecoder alloc] init]; 

    c. - (void)closeFile; 

    结束

     av_frame_free(&pFrame);

        avcodec_close(pCodecCtx); 

        avformat_close_input(&pFormatCtx);

    d. - (BOOL) setupVideoFrameFormat: (KxVideoFrameFormat) format; 

    枚举设置为 KxVideoFrameFormatRGB或者KxVideoFrameFormatYUV, 

    e.- (NSArray *) decodeFrames: (CGFloat) minDuration;

    通过AVFormatContext对象读取frames。需要方法a的操作做铺垫。

    1. 从 AVFormatContext读取下一个AVPacket。int av_read_frame (AVFormatContext *sAVPacket *pkt)
    2. 解码从AVPacket *avpkt转化为AVFrame *picture。。int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr,  const AVPacket *avpkt);   
    3. 帧速控制attribute_deprecated int avpicture_deinterlace (AVPicture *dst, const AVPicture *src, enum AVPixelFormat pix_fmt, int width, int height)      
    4. 返回frames的数组。

    2.KxAudioManager

    播放管理,例如paly,pause等,

    3.KxMovieGLView

    KxMovieDecoder提供解码的API,在vedio解码为YUV或者RGB文件。KxMovieGLView利用OpenGLES(绘图技术)呈现YUV文件。

    4.KxMovieViewController

    使用以上API,构建播放器界面

     

    五、总结

     

    我的学习步骤

    1.先学会使用系统的播放器进行视频播放

    2.学会使用第三方Kxmovie

    学会这两个,可以应付基本的视频开发

    3.深入学习AVFoundation框架 我买的这本书 AV Foundation开发秘籍:实践掌握iOS & OS X 应用的视听处理技术  我还没看完

    4.需要深入的话,需要多FFmpeg框架。当然需要先学习音视频开发的基础如RGB、YUV像素数据处理、PCM音频采样数据处理、H.264视频码流解析等等。好多啊。

     

    六、参考资料汇总———也是我自己总结的大家深入学习的一些资料吧

     

    HTTP Live Streaming直播(iOS直播)技术分析与实现:http://www.cnblogs.com/haibindev/archive/2013/01/30/2880764.html

    HTT Live Streaming官方文档:https://developer.apple.com/streaming/ 

    FFmpeg深入分析之零-基础  http://blog.chinaunix.net/uid-26611383-id-3976154.html

    一篇大学论文,很长但是能让小白了解iOS流媒体都需要什么 http://www.doc88.com/p-7098896030363.html 

    流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls) http://blog.csdn.net/tttyd/article/details/12032357 

    视频流传输协议RTP/RTCP/RTSP/HTTP的区别 http://blog.csdn.net/yangxt/article/details/7467457

    ffmpeg框架解读 http://blog.csdn.net/allen_young_yang/article/details/6576303 

    流媒体博客  http://blog.csdn.net/leixiaohua1020/article/details/15811977 

    http://blog.csdn.net/beitiandijun/article/details/8280448  FFmpeg的基本概念

    视频编码标准汇总及比较 http://blog.csdn.net/leixiaohua1020/article/details/12031631

    视音频编解码技术零基础学习方法 http://blog.csdn.net/leixiaohua1020/article/details/18893769

     

    书籍:AV Foundation开发秘籍:实践掌握iOS & OS X 应用的视听处理技术 

     

    七、水平有限,作者也是刚开始学习,肯定有很多不正确的地方,希望大家指正,谢谢。

    展开全文
  • iOS中关于媒体的简介:介于下载本地播放与实时媒体之间的一种播放形式,下载本地播放必须全部将文件下载完成后才能播放,而渐进式下载不必等到全部下载完成后再播放,它可以一边下载一边播放,在完成播放内容...

             iOS中关于流媒体的简介:介于下载本地播放与实时流媒体之间的一种播放形式,下载本地播放必须全部将文件下载完成后才能播放,而渐进式下载不必等到全部下载完成后再播放,它可以一边下载一边播放,在完成播放内容之后,整个文件会保存在手机上。

    实时流媒体

    实时流媒体是一边接收数据包一边播放,本地不保留文件副本,实时流式传输总是实时传送,可以实时实况转播,支持随机访问,用户可以快进或者快退以观看前面或后面的内容。实时流媒体传输必须保证数据包的传输速度大于文件的播放速度,否则用户看到的视频会出现暂停。当网络堵塞情况下视频质量会下降,所以要想保证视频的质量渐进式下载会更好一些。

    下面是本人亲测的流媒体播放和下载教程:

    =====================================================

    1.界面搭建(如图)

    2.用到的第三方助手类(需要的可以微博互动或私信我)

    下载地址:http://pan.baidu.com/s/1hrvqXA8

    3.开始项目-头文件及相关宏


    LO_ViewController.h

    #import <UIKit/UIKit.h>
    #import <MediaPlayer/MediaPlayer.h>
    #import "M3U8Handler.h"
    #import "VideoDownloader.h"
    #import "HTTPServer.h"
    
    @interface LO_ViewController : UIViewController<M3U8HandlerDelegate,VideoDownloadDelegate>
    
    @property (nonatomic, strong)HTTPServer * httpServer;
    @property (nonatomic, strong)VideoDownloader *downloader;
    
    @end

    LO_ViewController.m

    #import "LO_ViewController.h"
    
    @interface LO_ViewController ()
    
    @property (weak, nonatomic) IBOutlet UIProgressView *progressView;
    
    @property (weak, nonatomic) IBOutlet UILabel *progressLabel;
    
    @property (weak, nonatomic) IBOutlet UIButton *downloadButton;
    
    @property (weak, nonatomic) IBOutlet UIButton *clearButton;
    
    @end
    
    @implementation LO_ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    	// Do any additional setup after loading the view, typically from a nib.
        
        //打开本地服务器
        [self openHttpServer];
        
        if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"isDownload"] boolValue]) {
            [self.downloadButton setTitle:@"已完成" forState:UIControlStateNormal];
            self.downloadButton.enabled = NO;
            self.clearButton.enabled = YES;
            M3U8Handler *handler = [[M3U8Handler alloc] init];
            [handler praseUrl:[NSString stringWithFormat:@"http://v.youku.com/player/getM3U8/vid/XNjUxMTE4NDAw/type/mp4"]];
            handler.playlist.uuid = @"XNjUxMTE4NDAw";
            self.downloader = [[VideoDownloader alloc]initWithM3U8List:handler.playlist];
            [self.downloader addObserver:self forKeyPath:@"clearCaches" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; // 判断是否清理缓存
        }
        
        
    }

    #pragma mark - 打开本地服务器

    - (void)openHttpServer
    {
        self.httpServer = [[HTTPServer alloc] init];
        [self.httpServer setType:@"_http._tcp."];  // 设置服务类型
        [self.httpServer setPort:12345]; // 设置服务器端口
            
        // 获取本地Library/Cache路径下downloads路径
        NSString *webPath = [kLibraryCache stringByAppendingPathComponent:kPathDownload];
        NSLog(@"-------------\nSetting document root: %@\n", webPath);
    	
        // 设置服务器路径
    	[self.httpServer setDocumentRoot:webPath];
        NSError *error;
        
    	if(![self.httpServer start:&error])
    	{
            NSLog(@"-------------\nError starting HTTP Server: %@\n", error);
    	}
    }
    

    #pragma mark - 清理缓存
    - (IBAction)clearCaches:(id)sender {
        [self.downloader cleanDownloadFiles];
    }

    #pragma mark - 在线流媒体播放

    - (IBAction)playStreamingMedia:(id)sender {
        
        // 优酷视频m3u8新地址格式如下:http://pl.youku.com/playlist/m3u8?vid=XNjUxMTE4NDAw&type=mp4
        // 如果上面的链接不可用,那么使用这个链接http://v.youku.com/player/getM3U8/vid/XNjUxMTE4NDAw/type/mp4,如果两个连接都不可以用的话,那么很大可能是优酷的服务器挂掉了
        // 如果上面的两种格式都不行的话,考虑用这个格式,当然如果这个格式不行的话,是上面的,或者直接换个对应的m3u8的地址 http://pl.youku.com/playlist/m3u8?vid=162779600&ts=1407469897&ctype=12&token=3357&keyframe=1&sid=640746989782612d6cc70&ev=1&type=mp4&ep=dCaUHU2LX8YJ4ivdjj8bMyqxJ3APXP8M9BiCiNRiANQnS%2B24&oip=2043219268
        NSURL *url = [[NSURL alloc] initWithString:@"http://pl.youku.com/playlist/m3u8?vid=162779600&ts=1407469897&ctype=12&token=3357&keyframe=1&sid=640746989782612d6cc70&ev=1&type=flv&ep=dCaUHU2LX8YJ4ivdjj8bMyqxJ3APXP8M9BiCiNRiANQnS%2B24&oip=2043219268"];
        MPMoviePlayerViewController *player = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
        
        [self presentMoviePlayerViewControllerAnimated:player];
        
    }
    

    #pragma mark - 视频下载

    - (IBAction)downloadStreamingMedia:(id)sender {
        
        UIButton *downloadButton = sender;
        
        // 获取本地Library/Cache路径
        NSString *localDownloadsPath = [kLibraryCache stringByAppendingPathComponent:kPathDownload];
        // 获取视频本地路径
        NSString *filePath = [localDownloadsPath stringByAppendingPathComponent:@"XNjUxMTE4NDAw/movie.m3u8"];
        NSFileManager *fileManager = [NSFileManager defaultManager];
        
        // 判断视频是否缓存完成,如果完成则播放本地缓存
        if ([fileManager fileExistsAtPath:filePath]) {
            [downloadButton setTitle:@"已完成" forState:UIControlStateNormal];
            downloadButton.enabled = NO;
        
        }else{
            M3U8Handler *handler = [[M3U8Handler alloc] init];
            handler.delegate = self;
            // 解析m3u8视频地址
            [handler praseUrl:@"http://pl.youku.com/playlist/m3u8?vid=162779600&ts=1407469897&ctype=12&token=3357&keyframe=1&sid=640746989782612d6cc70&ev=1&type=flv&ep=dCaUHU2LX8YJ4ivdjj8bMyqxJ3APXP8M9BiCiNRiANQnS%2B24&oip=2043219268"];
            
            // 开启网络指示器
            [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
        }
    }
    

    #pragma mark - 播放本地缓存视频

    - (IBAction)playVideoFromLocal:(id)sender {
        
        NSString * playurl = [NSString stringWithFormat:@"http://127.0.0.1:12345/XNjUxMTE4NDAw/movie.m3u8"];
        NSLog(@"本地视频地址-----%@", playurl);
        
        // 获取本地Library/Cache路径
        NSString *localDownloadsPath = [kLibraryCache stringByAppendingPathComponent:kPathDownload];
        // 获取视频本地路径
        NSString *filePath = [localDownloadsPath stringByAppendingPathComponent:@"XNjUxMTE4NDAw/movie.m3u8"];
        NSFileManager *fileManager = [NSFileManager defaultManager];
        
        // 判断视频是否缓存完成,如果完成则播放本地缓存
        if ([fileManager fileExistsAtPath:filePath]) {
            MPMoviePlayerViewController *playerViewController =[[MPMoviePlayerViewController alloc]initWithContentURL:[NSURL URLWithString: playurl]];
            [self presentMoviePlayerViewControllerAnimated:playerViewController];
        }
        else{
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Sorry" message:@"当前视频未缓存" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
        }
    }
    
    #pragma mark -
    #pragma mark - 视频解析完成
    -(void)praseM3U8Finished:(M3U8Handler*)handler
    {
        handler.playlist.uuid = @"XNjUxMTE4NDAw";
        self.downloader = [[VideoDownloader alloc]initWithM3U8List:handler.playlist];
        [self.downloader addObserver:self forKeyPath:@"currentProgress" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; // 设置观察者用来得到当前下载的进度
        [self.downloader addObserver:self forKeyPath:@"clearCaches" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; // 判断是否清理缓存
        self.downloader.delegate = self;
        [self.downloader startDownloadVideo]; // 开始下载
    }
    
    
    每日更新关注:http://weibo.com/hanjunqiang  新浪微博

    #pragma mark - 通过观察者监控下载进度显示/缓存清理

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        if ([keyPath isEqualToString:@"clearCaches"]) {
            self.downloadButton.enabled = YES;
            [self.downloadButton setTitle:@"下载" forState:UIControlStateNormal];
            self.clearButton.enabled = NO;
            [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"isDownload"];
            [[NSUserDefaults standardUserDefaults] synchronize];
            self.progressView.progress = 0.0;
            self.progressLabel.text = [NSString stringWithFormat:@"%.2f%%", 0.0];
        }else{
            self.progressLabel.text = [NSString stringWithFormat:@"%.2f%%", 100 * [[change objectForKey:@"new"] floatValue]];
            self.progressView.progress = [[change objectForKey:@"new"] floatValue];
            if (self.progressView.progress == 1) {
                [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"isDownload"];
                [self.downloadButton setTitle:@"已完成" forState:UIControlStateNormal];
                [[NSUserDefaults standardUserDefaults] synchronize];
                self.clearButton.enabled = YES;
                self.downloadButton.enabled = NO;
            }
        }
        
    }

    #pragma mark - 视频解析失败
    -(void)praseM3U8Failed:(M3U8Handler*)handler
    {
        NSLog(@"视频解析失败-failed -- %@",handler);
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"噢,NO~


    iOS开发者交流群:446310206


    展开全文
  • iOS硬解码H264视频流

    2018-10-30 15:11:17
    苹果在iOS 8.0系统之前若要做音视频开发需使用第三方软件进行编解码(FFmpeg软解码H264视频流可看到这里),学习成本较大,项目开发进度也可能超出预期。在iOS 8.0之后开放了视频编解码框架VideoToolbox,在此之后对于...

            苹果在iOS 8.0系统之前若要做音视频开发需使用第三方软件进行编解码(FFmpeg软解码H264视频流可看到这里),学习成本较大,项目开发进度也可能超出预期。在iOS 8.0之后开放了视频编解码框架VideoToolbox,在此之后对于音视频开发变得相对简单。

     

     

     

    一、硬解码名词(结构)解释

    1、VTDecompressionSessionRef:解码器对象数据结构;

    2、CMVideoFormatDescriptionRef:图形解码相关格式及描述;

    3、CVPixelBufferRef:编码前和解码后的图像数据结构;

    4、CMBlockBufferRef:存在解码前图像数据内存结构;

    5、CMSampleBufferRef:存放解码前的视频图像的容器数据结构;

    6、AVSampleBufferDisplayLayer:以CMSampleBufferRef进行解码并显示Layer图层;

    7、SPSPPS:h.264解码参数信息;IDR:h.264视频流I帧;

    二、H264硬解码流程图

     

    三:IDR(I帧)网络裸流数据结构

            一般情况下网络视频裸流I帧中基本会包含SPS、PPS、SEI、IDR帧数据,如下图所示,但是部分只含有IDR帧数据,其他解码参数信息被单独已Slice获取。

     

    四、硬解码相关接口

     

    1、初始化H264硬解解码器

    1)使用CMVideoFormatDescriptionCreateFromH264ParameterSets函数构建解码描述结构CMVideoFormatDescriptionRef

        const uint8_t *const parameterSetPointers[2] = {pSPS,pPPS};
        const size_t parameterSetSizes[2] = {mSpsSize, mPpsSize};
        
        OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
                                                                              2,    //参数个数,主要包含SPS、PPS
                                                                              parameterSetPointers,
                                                                              parameterSetSizes,
                                                                              4,    //NALU起始位个数
                                                                              &mDecoderFormatDescription);

    2)使用VTDecompressionSessionCreate函数构建解码器结构VTDecompressionSessionRef

     

        uint32_t pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;  //NV12
        const void *keys[] = { kCVPixelBufferPixelFormatTypeKey };
        const void *values[] = { CFNumberCreate(NULL, kCFNumberSInt32Type, &pixelFormatType) };    //32位
        CFDictionaryRef attrs = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
        
        VTDecompressionOutputCallbackRecord callBackRecord;
        callBackRecord.decompressionOutputCallback = didDecompress;    
        callBackRecord.decompressionOutputRefCon = NULL;
        
        status = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                              mDecoderFormatDescription,
                                              NULL, attrs,
                                              &callBackRecord,
                                              &mDeocderSession);
        CFRelease(attrs);

    2、H264硬件解码

    1)将视频裸流数据构建成CMBlockBufferRef,主要目的是进一步转换为CMSampleBufferRef

        CMBlockBufferRef blockBuffer = NULL;
        OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, (void *)videoBuffer, videoBufferSize, kCFAllocatorNull, NULL, 0, videoBufferSize, 0, &blockBuffer);
        CMSampleBufferRef sampleBuffer = NULL;
        const size_t sampleSizeArray[] = { videoBufferSize };
        OSStatus status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, mDecoderFormatDescription , 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);

    2)将CMSampleBufferRef结构送入VTDecompressionSessionDecodeFrame函数进行解码处理:

        VTDecodeFrameFlags flags = 0;
        VTDecodeInfoFlags flagOut = 0;
        CVPixelBufferRef outputPixelBuffer = NULL;
        OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(mDeocderSession, sampleBuffer, flags, &outputPixelBuffer, &flagOut);

    3)若使用AVSampleBufferDisplayLayer图层进行直接显示,可忽略上一步的还行,直接将CMSampleBufferRef送入AVSampleBufferDisplayLayer进行显示:

        CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
        CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
        if ([self.displayLayer isReadyForMoreMediaData]) {
            @weakify(self);
            dispatch_sync(dispatch_get_main_queue(),^{
                @strongify(self);
                [self.displayLayer enqueueSampleBuffer:sampleBuffer];
            });
        }

    3、解码之后的数据显示

            在本文中支持3种显示方式:UIImageCVPixelBufferRefAVSampleBufferDisplayLayer,因在项目中需要UIImage,所以被默认转化模式。

    CVPixelBufferRef:即不进行UIImage转换而直接输出的方式;

    AVSampleBufferDisplayLayer:不进行代码逻辑解码,被Layer层自行解码和显示;

    UIImage:通过CVPixelBufferRef进一步转化所得(提供了2种转化方法,可在后面代码中查看):

        CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
        CIContext *temporaryContext = [CIContext contextWithOptions:nil];
        CGImageRef videoImage = [temporaryContext createCGImage:ciImage fromRect:CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer))];
        image = [[UIImage alloc] initWithCGImage:videoImage];
        CGImageRelease(videoImage);

    五、完整H264解码代码

            本人原则上主张自主编写相关代码并学习相应的知识,在此贴出iOS对H264视频裸流硬解码的完整代码,各位可进行参考或学习,若存在问题或者疑问欢迎留言。代码中关于CLog接口为打印输出,可自行屏蔽。

    //
    //  H264HwDecoder.h
    //  IOTCamera
    //
    //  Created by lzj<lizhijian_21@163.com> on 2017/2/18.
    //  Copyright (c) 2017 LZJ. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    #import <VideoToolbox/VideoToolbox.h>
    #import <AVFoundation/AVSampleBufferDisplayLayer.h>
    
    typedef enum : NSUInteger {
        H264HWDataType_Image = 0,
        H264HWDataType_Pixel,
        H264HWDataType_Layer,
    } H264HWDataType;
    
    @interface H264HwDecoder : NSObject
    
    @property (nonatomic,assign) H264HWDataType showType;    //显示类型
    @property (nonatomic,strong) UIImage *image;            //解码成RGB数据时的IMG
    @property (nonatomic,assign) CVPixelBufferRef pixelBuffer;    //解码成YUV数据时的解码BUF
    @property (nonatomic,strong) AVSampleBufferDisplayLayer *displayLayer;  //显示图层
    
    @property (nonatomic,assign) BOOL isNeedPerfectImg;    //是否读取完整UIImage图形(showType为0时才有效)
    
    - (instancetype)init;
    
    /**
     H264视频流解码
    
     @param videoData 视频帧数据
     @param videoSize 视频帧大小
     @return 视图的宽高(width, height),当为接收为AVSampleBufferDisplayLayer时返回接口是无效的
     */
    - (CGSize)decodeH264VideoData:(uint8_t *)videoData videoSize:(NSInteger)videoSize;
    
    /**
     释放解码器
     */
    - (void)releaseH264HwDecoder;
    
    /**
     视频截图
    
     @return IMG
     */
    - (UIImage *)snapshot;
    
    @end
    
    //
    //  H264HwDecoder.m
    //  IOTCamera
    //
    //  Created by lzj<lizhijian_21@163.com> on 2017/2/18.
    //  Copyright (c) 2017 LZJ. All rights reserved.
    //
    
    #import "H264HwDecoder.h"
    
    #ifndef FreeCharP
    #define FreeCharP(p) if (p) {free(p); p = NULL;}
    #endif
    
    typedef enum : NSUInteger {
        HWVideoFrameType_UNKNOWN = 0,
        HWVideoFrameType_I,
        HWVideoFrameType_P,
        HWVideoFrameType_B,
        HWVideoFrameType_SPS,
        HWVideoFrameType_PPS,
        HWVideoFrameType_SEI,
    } HWVideoFrameType;
    
    @interface H264HwDecoder ()
    {
        VTDecompressionSessionRef mDeocderSession;
        CMVideoFormatDescriptionRef mDecoderFormatDescription;
        
        uint8_t *pSPS;
        uint8_t *pPPS;
        uint8_t *pSEI;
        NSInteger mSpsSize;
        NSInteger mPpsSize;
        NSInteger mSeiSize;
        
        NSInteger mINalCount;        //I帧起始码个数
        NSInteger mPBNalCount;       //P、B帧起始码个数
        NSInteger mINalIndex;       //I帧起始码开始位
        
        BOOL mIsNeedReinit;         //需要重置解码器
    }
    
    @end
    
    static void didDecompress(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration )
    {
        CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
        *outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
    }
    
    @implementation H264HwDecoder
    
    - (instancetype)init
    {
        if (self = [super init]) {
            pSPS = pPPS = pSEI = NULL;
            mSpsSize = mPpsSize = mSeiSize = 0;
            mINalCount = mPBNalCount = mINalIndex = 0;
            mIsNeedReinit = NO;
            
            _showType = H264HWDataType_Image;
            _isNeedPerfectImg = NO;
            _pixelBuffer = NULL;
        }
        
        return self;
    }
    
    - (void)dealloc
    {
        [self releaseH264HwDecoder];
    }
    
    - (BOOL)initH264HwDecoder
    {
        if (mDeocderSession) {
            return YES;
        }
        
        const uint8_t *const parameterSetPointers[2] = {pSPS,pPPS};
        const size_t parameterSetSizes[2] = {mSpsSize, mPpsSize};
        
        OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, 4, &mDecoderFormatDescription);
        
        if (status == noErr) {
            //      kCVPixelFormatType_420YpCbCr8Planar is YUV420
            //      kCVPixelFormatType_420YpCbCr8BiPlanarFullRange is NV12
            //      kCVPixelFormatType_24RGB    //使用24位bitsPerPixel
            //      kCVPixelFormatType_32BGRA   //使用32位bitsPerPixel,kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst
        uint32_t pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;  //NV12
        if (self.showType == H264HWDataType_Pixel) {
            pixelFormatType = kCVPixelFormatType_420YpCbCr8Planar;
        }
        const void *keys[] = { kCVPixelBufferPixelFormatTypeKey };
        const void *values[] = { CFNumberCreate(NULL, kCFNumberSInt32Type, &pixelFormatType) };
        CFDictionaryRef attrs = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
        
        VTDecompressionOutputCallbackRecord callBackRecord;
        callBackRecord.decompressionOutputCallback = didDecompress;
        callBackRecord.decompressionOutputRefCon = NULL;
        
        status = VTDecompressionSessionCreate(kCFAllocatorDefault,
                                              mDecoderFormatDescription,
                                              NULL, attrs,
                                              &callBackRecord,
                                              &mDeocderSession);
        CFRelease(attrs);
            CLog(@"Init H264 hardware decoder success");
        } else {
            CLog([NSString stringWithFormat:@"Init H264 hardware decoder fail: %d", (int)status]);
            return NO;
        }
        
        return YES;
    }
    
    - (void)removeH264HwDecoder
    {
        if(mDeocderSession) {
            VTDecompressionSessionInvalidate(mDeocderSession);
            CFRelease(mDeocderSession);
            mDeocderSession = NULL;
        }
        
        if(mDecoderFormatDescription) {
            CFRelease(mDecoderFormatDescription);
            mDecoderFormatDescription = NULL;
        }
    }
    
    - (void)releaseH264HwDecoder
    {
        [self removeH264HwDecoder];
        [self releaseSliceInfo];
        
        if (_pixelBuffer) {
            CVPixelBufferRelease(_pixelBuffer);
            _pixelBuffer = NULL;
        }
    }
    
    - (void)releaseSliceInfo
    {
        FreeCharP(pSPS);
        FreeCharP(pPPS);
        FreeCharP(pSEI);
        
        mSpsSize = 0;
        mPpsSize = 0;
        mSeiSize = 0;
    }
    
    //将视频数据封装成CMSampleBufferRef进行解码
    - (CVPixelBufferRef)decode:(uint8_t *)videoBuffer videoSize:(NSInteger)videoBufferSize
    {
        CVPixelBufferRef outputPixelBuffer = NULL;
        CMBlockBufferRef blockBuffer = NULL;
        OSStatus status  = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, (void *)videoBuffer, videoBufferSize, kCFAllocatorNull, NULL, 0, videoBufferSize, 0, &blockBuffer);
        if (status == kCMBlockBufferNoErr) {
            CMSampleBufferRef sampleBuffer = NULL;
            const size_t sampleSizeArray[] = { videoBufferSize };
            status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, mDecoderFormatDescription , 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
            
            if (status == kCMBlockBufferNoErr && sampleBuffer) {
                if (self.showType == H264HWDataType_Layer && _displayLayer) {
                    CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
                    CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
                    CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
                    if ([self.displayLayer isReadyForMoreMediaData]) {
                        @weakify(self);
                        dispatch_sync(dispatch_get_main_queue(),^{
                            @strongify(self);
                            [self.displayLayer enqueueSampleBuffer:sampleBuffer];
                        });
                    }
                    
                    CFRelease(sampleBuffer);
                } else {
                    VTDecodeFrameFlags flags = 0;
                    VTDecodeInfoFlags flagOut = 0;
                    OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(mDeocderSession, sampleBuffer, flags, &outputPixelBuffer, &flagOut);
                    CFRelease(sampleBuffer);
                    if (decodeStatus == kVTVideoDecoderMalfunctionErr) {
                        CLog(@"Decode failed status: kVTVideoDecoderMalfunctionErr");
                        CVPixelBufferRelease(outputPixelBuffer);
                        outputPixelBuffer = NULL;
                    } else if(decodeStatus == kVTInvalidSessionErr) {
                        CLog(@"Invalid session, reset decoder session");
                        [self removeH264HwDecoder];
                    } else if(decodeStatus == kVTVideoDecoderBadDataErr) {
                        CLog([NSString stringWithFormat:@"Decode failed status=%d(Bad data)", (int)decodeStatus]);
                    } else if(decodeStatus != noErr) {
                        CLog([NSString stringWithFormat:@"Decode failed status=%d", (int)decodeStatus]);
                    }
                }
            }
            
            CFRelease(blockBuffer);
        }
        
        return outputPixelBuffer;
    }
    
    - (CGSize)decodeH264VideoData:(uint8_t *)videoData videoSize:(NSInteger)videoSize
    {
        CGSize imageSize = CGSizeMake(0, 0);
        if (videoData && videoSize > 0) {
            HWVideoFrameType frameFlag = [self analyticalData:videoData size:videoSize];
            if (mIsNeedReinit) {
                mIsNeedReinit = NO;
                [self removeH264HwDecoder];
            }
            
            if (pSPS && pPPS && (frameFlag == HWVideoFrameType_I || frameFlag == HWVideoFrameType_P || frameFlag == HWVideoFrameType_B)) {
                uint8_t *buffer = NULL;
                if (frameFlag == HWVideoFrameType_I) {
                    int nalExtra = (mINalCount==3?1:0);      //如果是3位的起始码,转为大端时需要增加1位
                    videoSize -= mINalIndex;
                    buffer = (uint8_t *)malloc(videoSize + nalExtra);
                    memcpy(buffer + nalExtra, videoData + mINalIndex, videoSize);
                    videoSize += nalExtra;
                } else {
                    int nalExtra = (mPBNalCount==3?1:0);
                    buffer = (uint8_t *)malloc(videoSize + nalExtra);
                    memcpy(buffer + nalExtra, videoData, videoSize);
                    videoSize += nalExtra;
                }
                
                uint32_t nalSize = (uint32_t)(videoSize - 4);
                uint32_t *pNalSize = (uint32_t *)buffer;
                *pNalSize = CFSwapInt32HostToBig(nalSize);
                
                CVPixelBufferRef pixelBuffer = NULL;
                if ([self initH264HwDecoder]) {
                    pixelBuffer = [self decode:buffer videoSize:videoSize];
                    
                    if(pixelBuffer) {
                        NSInteger width = CVPixelBufferGetWidth(pixelBuffer);
                        NSInteger height = CVPixelBufferGetHeight(pixelBuffer);
                        imageSize = CGSizeMake(width, height);
                        
                        if (self.showType == H264HWDataType_Pixel) {
                            if (_pixelBuffer) {
                                CVPixelBufferRelease(_pixelBuffer);
                            }
                            self.pixelBuffer = CVPixelBufferRetain(pixelBuffer);
                        } else {
                            if (frameFlag == HWVideoFrameType_B) {  //若B帧未进行乱序解码,顺序播放,则在此需要去除,否则解码图形则是灰色。
                                size_t planeCount = CVPixelBufferGetPlaneCount(pixelBuffer);
                                if (planeCount >= 2 && planeCount <= 3) {
                                    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
                                    u_char *yDestPlane = (u_char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
                                    if (planeCount == 2) {
                                        u_char *uvDestPlane = (u_char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
                                        if (yDestPlane[0] == 0x80 && uvDestPlane[0] == 0x80 && uvDestPlane[1] == 0x80) {
                                            frameFlag = HWVideoFrameType_UNKNOWN;
                                            NSLog(@"Video YUV data parse error: Y=%02x U=%02x V=%02x", yDestPlane[0], uvDestPlane[0], uvDestPlane[1]);
                                        }
                                    } else if (planeCount == 3) {
                                        u_char *uDestPlane = (u_char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
                                        u_char *vDestPlane = (u_char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 2);
                                        if (yDestPlane[0] == 0x80 && uDestPlane[0] == 0x80 && vDestPlane[0] == 0x80) {
                                            frameFlag = HWVideoFrameType_UNKNOWN;
                                            NSLog(@"Video YUV data parse error: Y=%02x U=%02x V=%02x", yDestPlane[0], uDestPlane[0], vDestPlane[0]);
                                        }
                                    }
                                    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
                                }
                            }
                            
                            if (frameFlag != HWVideoFrameType_UNKNOWN) {
                                self.image = [self pixelBufferToImage:pixelBuffer];
                            }
                        }
                        
                        CVPixelBufferRelease(pixelBuffer);
                    }
                }
                
                FreeCharP(buffer);
            }
        }
        
        return imageSize;
    }
    
    - (UIImage *)pixelBufferToImage:(CVPixelBufferRef)pixelBuffer
    {
        UIImage *image = nil;
        if (!self.isNeedPerfectImg) {
            //第1种绘制(可直接显示,不可保存为文件(无效缺少图像描述参数))
            CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
            image = [UIImage imageWithCIImage:ciImage];
        } else {
            //第2种绘制(可直接显示,可直接保存为文件,相对第一种性能消耗略大)
        CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
        CIContext *temporaryContext = [CIContext contextWithOptions:nil];
        CGImageRef videoImage = [temporaryContext createCGImage:ciImage fromRect:CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer))];
        image = [[UIImage alloc] initWithCGImage:videoImage];
        CGImageRelease(videoImage);
        }
        
        return image;
    }
    
    - (UIImage *)snapshot
    {
        UIImage *img = nil;
        if (self.displayLayer) {
            UIGraphicsBeginImageContext(self.displayLayer.bounds.size);
            [self.displayLayer renderInContext:UIGraphicsGetCurrentContext()];
            img = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
        } else {
            if (self.showType == H264HWDataType_Pixel) {
                if (self.pixelBuffer) {
                    img = [self pixelBufferToImage:self.pixelBuffer];
                }
            } else {
                img = self.image;
            }
            
            if (!self.isNeedPerfectImg) {
                UIGraphicsBeginImageContext(CGSizeMake(img.size.width, img.size.height));
                [img drawInRect:CGRectMake(0, 0, img.size.width, img.size.height)];
                img = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
            }
        }
        
        return img;
    }
    
    
    //从起始位开始查询SPS、PPS、SEI、I、B、P帧起始码,遇到I、P、B帧则退出
    //存在多种情况:
    //1、起始码是0x0 0x0 0x0 0x01 或 0x0 0x0 0x1
    //2、每个SPS、PPS、SEI、I、B、P帧为单独的Slice
    //3、I帧中包含SPS、PPS、I数据Slice
    //4、I帧中包含第3点的数据之外还包含SEI,顺序:SPS、PPS、SEI、I
    //5、起始位是AVCC协议格式的大端数据(不支持多Slice的视频帧)
    - (HWVideoFrameType)analyticalData:(const uint8_t *)buffer size:(NSInteger)size
    {
        NSInteger preIndex = 0;
        HWVideoFrameType preFrameType = HWVideoFrameType_UNKNOWN;
        HWVideoFrameType curFrameType = HWVideoFrameType_UNKNOWN;
        for (int i=0; i<size && i<300; i++) {       //一般第四种情况下的帧起始信息不会超过(32+256+12)位,可适当增大,为了不循环整个帧片数据
            int nalSize = [self getNALHeaderLen:(buffer + i) size:size-i];
            if (nalSize == 0 && i == 0) {   //当每个Slice起始位开始若使用AVCC协议则判断帧大小是否一致
                uint32_t *pNalSize = (uint32_t *)(buffer);
                uint32_t videoSize = CFSwapInt32BigToHost(*pNalSize);    //大端模式转为系统端模式
                if (videoSize == size - 4) {     //是大端模式(AVCC)
                    nalSize = 4;
                }
            }
            
            if (nalSize && i + nalSize + 1 < size) {
                int sliceType = buffer[i + nalSize] & 0x1F;
                
                if (sliceType == 0x1) {
                    mPBNalCount = nalSize;
                    if (buffer[i + nalSize] == 0x1) {   //B帧
                        curFrameType = HWVideoFrameType_B;
                    } else {    //P帧
                        curFrameType = HWVideoFrameType_P;
                    }
                    break;
                } else if (sliceType == 0x5) {     //IDR(I帧)
                    if (preFrameType == HWVideoFrameType_PPS) {
                        mIsNeedReinit = [self getSliceInfo:buffer slice:&pPPS size:&mPpsSize start:preIndex end:i];
                    } else if (preFrameType == HWVideoFrameType_SEI)  {
                        [self getSliceInfo:buffer slice:&pSEI size:&mSeiSize start:preIndex end:i];
                    }
                    
                    mINalCount = nalSize;
                    mINalIndex = i;
                    curFrameType = HWVideoFrameType_I;
                    goto Goto_Exit;
                } else if (sliceType == 0x7) {      //SPS
                    preFrameType = HWVideoFrameType_SPS;
                    preIndex = i + nalSize;
                    i += nalSize;
                } else if (sliceType == 0x8) {      //PPS
                    if (preFrameType == HWVideoFrameType_SPS) {
                        mIsNeedReinit = [self getSliceInfo:buffer slice:&pSPS size:&mSpsSize start:preIndex end:i];
                    }
                    
                    preFrameType = HWVideoFrameType_PPS;
                    preIndex = i + nalSize;
                    i += nalSize;
                } else if (sliceType == 0x6) {      //SEI
                    if (preFrameType == HWVideoFrameType_PPS) {
                        mIsNeedReinit = [self getSliceInfo:buffer slice:&pPPS size:&mPpsSize start:preIndex end:i];
                    }
                    
                    preFrameType = HWVideoFrameType_SEI;
                    preIndex = i + nalSize;
                    i += nalSize;
                }
            }
        }
        
        //SPS、PPS、SEI为单独的Slice帧片
        if (curFrameType == HWVideoFrameType_UNKNOWN && preIndex != 0) {
            if (preFrameType == HWVideoFrameType_SPS) {
                mIsNeedReinit = [self getSliceInfo:buffer slice:&pSPS size:&mSpsSize start:preIndex end:size];
                curFrameType = HWVideoFrameType_SPS;
            } else if (preFrameType == HWVideoFrameType_PPS) {
                 mIsNeedReinit = [self getSliceInfo:buffer slice:&pPPS size:&mPpsSize start:preIndex end:size];
                curFrameType = HWVideoFrameType_PPS;
            } else if (preFrameType == HWVideoFrameType_SEI)  {
                [self getSliceInfo:buffer slice:&pSEI size:&mSeiSize start:preIndex end:size];
                curFrameType = HWVideoFrameType_SEI;
            }
        }
        
    Goto_Exit:
        return curFrameType;
    }
    
    //获取NAL的起始码长度是3还4
    - (int)getNALHeaderLen:(const uint8_t *)buffer size:(NSInteger)size
    {
        if (size >= 4 && buffer[0] == 0x0 && buffer[1] == 0x0 && buffer[2] == 0x0 && buffer[3] == 0x1) {
            return 4;
        } else if (size >= 3 && buffer[0] == 0x0 && buffer[1] == 0x0 && buffer[2] == 0x1) {
            return 3;
        }
        
        return 0;
    }
    
    //给SPS、PPS、SEI的Buf赋值,返回YES表示不同于之前的值
    - (BOOL)getSliceInfo:(const uint8_t *)videoBuf slice:(uint8_t **)sliceBuf size:(NSInteger *)size start:(NSInteger)start end:(NSInteger)end
    {
        BOOL isDif = NO;
        
        NSInteger len = end - start;
        uint8_t *tempBuf = (uint8_t *)(*sliceBuf);
        if (tempBuf) {
            if (len != *size || memcmp(tempBuf, videoBuf + start, len) != 0) {
                free(tempBuf);
                tempBuf = (uint8_t *)malloc(len);
                memcpy(tempBuf, videoBuf + start, len);
                
                *sliceBuf = tempBuf;
                *size = len;
                
                isDif = YES;
            }
        } else {
            tempBuf = (uint8_t *)malloc(len);
            memcpy(tempBuf, videoBuf + start, len);
            
            *sliceBuf = tempBuf;
            *size = len;
        }
        
        return isDif;
    }
    
    @end
    
    展开全文
  • 如题,IOS 上如何播放RTSP协议的视频流视频流的编码是H264的。 有做过的大虾,请不吝赐教,给点思路、资料。 最好有Demo. @_@
  • 现如今所有的短视频平台在音视频传输方面大都使用的是媒体传输,至于服务器方面也不是我们敲段代码就能搞定的,那么站在Ios视频开发的角度上来看初始化短视频方面我们能够做的是什么呢?我们可以敲代码让初...
  • 下面是做好的demo的样子。...下载OpenCV for iOS 我下载的是3.1版本。链接点进去没反应的自行翻墙一下。 然后将里面的framework直接拖入到iOS工程中。 界面什么的随便搭一下。 头文件引入 #import #
  • iOS视频通话/直播demo

    2020-07-13 10:11:05
    iOS/android视频通话/直播软件,视频编码器采用h264,经过优化的,码率低,cpu占用率低。音频采用aac。传输采用rtp,视频通话有进行...直播接收程序用rtsp请求服务器的音视频流,可以接收实时sdp流以及hint过的mp4流。
  •  原生开发基本流程:AVFoundation获取视频流,得到未编码的CMSampleBuffer,需要编码成Mpeg-4格式。编码分软编码和硬编码,考虑到iOS8之后VideoToolBox开放使用,选用VideoToolBox进行编码。坑爹的是针对它连
  • 最近视频直播功能比较火,处于需求,研究了一番,根据分析决定使用媒体实现,代码简单易懂,接下来看教程: 简单介绍: HLS 协议 : >5M会被AppStore拒绝 服务器要求低 延迟高 多平台 RTMP 协议: 电视直播 PC端...
  • IOS提供了一个比较好的视频框架MobileVLCKit,这个框架支持的视频格式和协议比较多,RTSP就是其中一种,上一篇文章中谈到使用MobileVLCKit来播放大华或者海康的视频流,这篇文章就来讲如何使用MobileVLCKit来搭建一...
  • ios视频流

    2016-03-14 15:48:37
    上回书说到iOS视频采集并使用AVCaptureMovieFileOutput类进行视频录制,以及使用AVCaptureVideoDataOutput,AVCaptureAudioDataOutput进行音视频流获取。本期我讲介绍如何进行视频文件的写入。 捕获视频流的录制...
  • iOS视频开发(一)

    2019-03-04 13:39:10
    iOS 框架 MediaPlayer 、 AudioToolbox 、 AudioUnit 、 AVFoundation 、 AVKit 、 OpenAL。其中MediaPlayer 、 AVFoundation 是基于 Objective-C/Swift 的; AudioToolBox 、 AudioUnit 和 OpenAL 是基于 C 的。 ...
  • 最近开发遇到一个点击按钮实现直播视频流截屏的功能,去网上查了一下资料,总结了一下iOS中截屏相关的知识,然后自己做了个demo。 demo主要实现了3种截屏方法,分别对应三种不同的应用场景。 1、imageView截图,这...
  • iOS开发直播视频流媒体移动应用,需要注意的4点问题。 1 不可扩展的架构 实时视频流是一种敏感问题,这些应用程序,性能是关键。 例如,希望尽可能减少发布者和订阅者之间的延迟,如果可能的话,不到2秒。 将...
  • 此文介绍语音视频云服务商即构科技提供的ios视频直播SDK集成指引。此SDK可实现主播和观众以一对一、一对多、或多对多的关系等,并支持小程序。 手动安装 目前 ZegoLiveRoom SDK 仅支持手动安装,集成步骤如下。 1...
1 2 3 4 5 ... 20
收藏数 26,653
精华内容 10,661
关键字:

ios视频流开发问题