精华内容
下载资源
问答
  • 本文主要通过对接收端的分析来了解和优化视频首的显示时间。 流程介绍 发送端采集音视频数据,通过编码器生成帧数据。这数据被打包成 RTP 包,通过 ICE 通道发送到接收端。接收端接收 RTP 包,取出 RTP payload,...

    音视频实时通话首帧的显示是一项重要的用户体验标准。本文主要通过对接收端的分析来了解和优化视频首帧的显示时间。

    流程介绍

    发送端采集音视频数据,通过编码器生成帧数据。这数据被打包成 RTP 包,通过 ICE 通道发送到接收端。接收端接收 RTP 包,取出 RTP payload,完成组帧的操作。之后音视频解码器解码帧数据,生成视频图像或音频 PCM 数据。

    在这里插入图片描述

    本文参数调整谈论的部分位于上图中的第 4 步。因为是接收端,所以会收到对方的 Offer 请求。先设置 SetRemoteDescription 再 SetLocalDescription。如下图蓝色部分:

    在这里插入图片描述

    参数调整

    视频参数调整

    当收到 Signal 线程 SetRemoteDescription 后,会在 Worker 线程中创建 VideoReceiveStream 对象。具体流程为 SetRemoteDescription
    VideoChannel::SetRemoteContent_w 创建 WebRtcVideoReceiveStream。
    WebRtcVideoReceiveStream 包含了一个 VideoReceiveStream 类型 stream_ 对象, 通过 webrtc::VideoReceiveStream* Call::CreateVideoReceiveStream 创建。创建后立即启动 VideoReceiveStream 工作,即调用 Start() 方法。此时 VideoReceiveStream 包含一个 RtpVideoStreamReceiver 对象准备开始处理 video RTP 包。接收方创建 createAnswer 后通过 setLocalDescription 设置 local descritpion。 对应会在 Worker 线程中 setLocalContent_w 方法中根据 SDP 设置 channel 的接收参数,最终会调用到 WebRtcVideoReceiveStream::SetRecvParameters。
    WebRtcVideoReceiveStream::SetRecvParameters 实现如下:

    void WebRtcVideoChannel::WebRtcVideoReceiveStream::SetRecvParameters(
        const ChangedRecvParameters& params) {
      bool video_needs_recreation = false;
      bool flexfec_needs_recreation = false;
      if (params.codec_settings) {
        ConfigureCodecs(*params.codec_settings);
        video_needs_recreation = true;
      }
      if (params.rtp_header_extensions) {
        config_.rtp.extensions = *params.rtp_header_extensions;
        flexfec_config_.rtp_header_extensions = *params.rtp_header_extensions;
        video_needs_recreation = true;
        flexfec_needs_recreation = true;
      }
      if (params.flexfec_payload_type) {
        ConfigureFlexfecCodec(*params.flexfec_payload_type);
        flexfec_needs_recreation = true;
      }
      if (flexfec_needs_recreation) {
        RTC_LOG(LS_INFO) << "MaybeRecreateWebRtcFlexfecStream (recv) because of "
                            "SetRecvParameters";
        MaybeRecreateWebRtcFlexfecStream();
      }
      if (video_needs_recreation) {
        RTC_LOG(LS_INFO)
            << "RecreateWebRtcVideoStream (recv) because of SetRecvParameters";
        RecreateWebRtcVideoStream();
      }
    }
    

    根据上图中 SetRecvParameters 代码,如果 codec_settings 不为空、rtp_header_extensions 不为空、flexfec_payload_type 不为空都会重启 VideoReceiveStream。video_needs_recreation 表示是否要重启 VideoReceiveStream。重启过程为,把先前创建的释放掉,然后重建新的 VideoReceiveStream。以 codec_settings 为例,初始 video codec 支持 H264 和 VP8。若对端只支持 H264,协商后的 codec 仅支持 H264。SetRecvParameters 中的 codec_settings 为 H264 不空。其实前后 VideoReceiveStream 的都有 H264 codec,没有必要重建 VideoReceiveStream。可以通过配置本地支持的 video codec 初始列表和 rtp extensions,从而生成的 local SDP 和 remote SDP 中影响接收参数部分调整一致,并且判断 codec_settings 是否相等。 如果不相等再 video_needs_recreation 为 true。这样设置就会使 SetRecvParameters 避免触发重启 VideoReceiveStream 逻辑。 在 debug 模式下,修改后,验证没有 “RecreateWebRtcVideoStream (recv) because of SetRecvParameters” 的打印, 即可证明没有 VideoReceiveStream 重启。

    音频参数调整

    和上面的视频类似,音频也会有因为 rtp extensions 不一致导致重新创建 AudioReceiveStream,也是释放先前的 AudioReceiveStream,再重新创建 AudioReceiveStream。参考代码:

    bool WebRtcVoiceMediaChannel::SetRecvParameters(
        const AudioRecvParameters& params) {
      TRACE_EVENT0("webrtc", "WebRtcVoiceMediaChannel::SetRecvParameters");
      RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
      RTC_LOG(LS_INFO) << "WebRtcVoiceMediaChannel::SetRecvParameters: "
                       << params.ToString();
      // TODO(pthatcher): Refactor this to be more clean now that we have
      // all the information at once.
    
      if (!SetRecvCodecs(params.codecs)) {
        return false;
      }
    
      if (!ValidateRtpExtensions(params.extensions)) {
        return false;
      }
      std::vector<webrtc::RtpExtension> filtered_extensions = FilterRtpExtensions(
          params.extensions, webrtc::RtpExtension::IsSupportedForAudio, false);
      if (recv_rtp_extensions_ != filtered_extensions) {
        recv_rtp_extensions_.swap(filtered_extensions);
        for (auto& it : recv_streams_) {
          it.second->SetRtpExtensionsAndRecreateStream(recv_rtp_extensions_);
        }
      }
      return true;
    }
    

    AudioReceiveStream 的构造方法会启动音频设备,即调用 AudioDeviceModule 的 StartPlayout。AudioReceiveStream 的析构方法会停止音频设备,即调用 AudioDeviceModule 的 StopPlayout。因此重启 AudioReceiveStream 会触发多次 StartPlayout/StopPlayout。经测试,这些不必要的操作会导致进入视频会议的房间时,播放的音频有一小段间断的情况。解决方法同样是通过配置本地支持的 audio codec 初始列表和 rtp extensions,从而生成的 local SDP 和 remote SDP 中影响接收参数部分调整一致,避免 AudioReceiveStream 重启逻辑。另外 audio codec 多为 WebRTC 内部实现,去掉一些不用的 Audio Codec,可以减小 WebRTC 对应的库文件。

    音视频相互影响

    WebRTC 内部有三个非常重要的线程,woker 线程、signal 线程和 network 线程。
    调用 PeerConnection 的 API 的调用会由 signal 线程进入到 worker 线程。
    worker 线程内完成媒体数据的处理,network 线程处理网络相关的事务,channel.h 文件中有说明,以 _w 结尾的方法为 worker 线程的方法,signal 线程的到 worker 线程的调用是同步操作。如下图中的 InvokerOnWorker 是同步操作,setLocalContent_w 和 setRemoteContent_w 是 worker 线程中的方法。

    bool BaseChannel::SetLocalContent(const MediaContentDescription* content,
                                      SdpType type,
                                      std::string* error_desc) {
      TRACE_EVENT0("webrtc", "BaseChannel::SetLocalContent");
      return InvokeOnWorker<bool>(
          RTC_FROM_HERE,
          Bind(&BaseChannel::SetLocalContent_w, this, content, type, error_desc));
    }
    
    bool BaseChannel::SetRemoteContent(const MediaContentDescription* content,
                                       SdpType type,
                                       std::string* error_desc) {
      TRACE_EVENT0("webrtc", "BaseChannel::SetRemoteContent");
      return InvokeOnWorker<bool>(
          RTC_FROM_HERE,
          Bind(&BaseChannel::SetRemoteContent_w, this, content, type, error_desc));
    }
    

    setLocalDescription 和 setRemoteDescription 中的 SDP 信息都会通过 PeerConnection 的 PushdownMediaDescription 方法依次下发给 audio/video RtpTransceiver 设置 SDP 信息。举例,执行 audio 的 SetRemoteContent_w 执行很长(比如音频 AudioDeviceModule 的 InitPlayout 执行耗时), 会影响后面的 video SetRemoteContent_w 的设置时间。PushdownMediaDescription 代码:

    RTCError PeerConnection::PushdownMediaDescription(
        SdpType type,
        cricket::ContentSource source) {
      const SessionDescriptionInterface* sdesc =
          (source == cricket::CS_LOCAL ? local_description()
                                       : remote_description());
      RTC_DCHECK(sdesc);
    
      // Push down the new SDP media section for each audio/video transceiver.
      for (const auto& transceiver : transceivers_) {
        const ContentInfo* content_info =
            FindMediaSectionForTransceiver(transceiver, sdesc);
        cricket::ChannelInterface* channel = transceiver->internal()->channel();
        if (!channel || !content_info || content_info->rejected) {
          continue;
        }
        const MediaContentDescription* content_desc =
            content_info->media_description();
        if (!content_desc) {
          continue;
        }
        std::string error;
        bool success = (source == cricket::CS_LOCAL)
                           ? channel->SetLocalContent(content_desc, type, &error)
                           : channel->SetRemoteContent(content_desc, type, &error);
        if (!success) {
          LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, error);
        }
      }
      ...
    }
    

    其他影响首帧显示的问题

    Android 图像宽高 16 字节对齐

    AndroidVideoDecoder 是 WebRTC Android 平台上的视频硬解类。AndroidVideoDecoder 利用 MediaCodec API 完成对硬件解码器的调用。
    MediaCodec 有已下解码相关的 API:

    • dequeueInputBuffer:若大于 0,则是返回填充编码数据的缓冲区的索引,该操作为同步操作。
    • getInputBuffer:填充编码数据的 ByteBuffer 数组,结合 dequeueInputBuffer 返回值,可获取一个可填充编码数据的 ByteBuffer。
    • queueInputBuffer:应用将编码数据拷贝到 ByteBuffer 后,通过该方法告知 MediaCodec 已经填写的编码数据的缓冲区索引。
    • dequeueOutputBuffer:若大于 0,则是返回填充解码数据的缓冲区的索引,该操作为同步操作。
    • getOutputBuffer:填充解码数据的 ByteBuffer 数组,结合 dequeueOutputBuffer 返回值,可获取一个可填充解码数据的 ByteBuffer。
    • releaseOutputBuffer:告诉编码器数据处理完成,释放 ByteBuffer 数据。

    在实践当中发现,发送端发送的视频宽高需要 16 字节对齐。因为在某些 Android 手机上解码器需要 16 字节对齐。Android 上视频解码先是把待解码的数据通过 queueInputBuffer 给到 MediaCodec。然后通过 dequeueOutputBuffer 反复查看是否有解完的视频帧。若非 16 字节对齐,dequeueOutputBuffer 会有一次 MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED。而不是一上来就能成功解码一帧。经测试发现,帧宽高非 16 字节对齐会比 16 字节对齐的慢 100 ms 左右。

    服务器需转发关键帧请求

    iOS 移动设备上,WebRTC App应用进入后台后,视频解码由 VTDecompressionSessionDecodeFrame 返回 kVTInvalidSessionErr,表示解码
    session 无效。从而会触发观看端的关键帧请求给服务器。这里要求服务器必须转发接收端发来的关键帧请求给发送端。若服务器没有转发关键帧给发送端,接收端就会长时间没有可以渲染的图像,从而出现黑屏问题。这种情况下只能等待发送端自己生成关键帧,发送个接收端,从而使黑屏的接收端恢复正常。

    WebRTC 内部的一些丢弃数据逻辑举例

    WebRTC 从接受数据到解码器之间的过程中也会来验证数据的正确性。

    举例1

    PacketBuffer 中记录着当前缓存的最小的序号 first_seq_num_(这个值也是会被更新的)。 当 PacketBuffer 中 InsertPacket 时候,如果即将要插入的 packet 的序号 seq_num 小于 first_seq_num,这个 packet 会被丢弃掉。如果因此持续丢弃 packet,就会有视频不显示或卡顿的情况。

    举例2

    正常情况下 FrameBuffer 中帧的 picture id,时间戳都是一直正增长的。如果 FrameBuffer 收到 picture_id 比最后解码帧的 picture id 小时,分两种情况:

      1. 时间戳比最后解码帧的时间戳大,且是关键帧,就会保存下来;
      1. 除情况 1 之外的帧都会丢弃掉;
        代码如下:
      auto last_decoded_frame = decoded_frames_history_.GetLastDecodedFrameId();
      auto last_decoded_frame_timestamp =
          decoded_frames_history_.GetLastDecodedFrameTimestamp();
      if (last_decoded_frame && id <= *last_decoded_frame) {
        if (AheadOf(frame->Timestamp(), *last_decoded_frame_timestamp) &&
            frame->is_keyframe()) {
          // If this frame has a newer timestamp but an earlier picture id then we
          // assume there has been a jump in the picture id due to some encoder
          // reconfiguration or some other reason. Even though this is not according
          // to spec we can still continue to decode from this frame if it is a
          // keyframe.
          RTC_LOG(LS_WARNING)
              << "A jump in picture id was detected, clearing buffer.";
          ClearFramesAndHistory();
          last_continuous_picture_id = -1;
        } else {
          RTC_LOG(LS_WARNING) << "Frame with (picture_id:spatial_id) ("
                              << id.picture_id << ":"
                              << static_cast<int>(id.spatial_layer)
                              << ") inserted after frame ("
                              << last_decoded_frame->picture_id << ":"
                              << static_cast<int>(last_decoded_frame->spatial_layer)
                              << ") was handed off for decoding, dropping frame.";
          return last_continuous_picture_id;
        }
      }
    

    因此为了能让收到了流顺利播放,发送端和中转的服务端需要确保视频帧的 picture_id, 时间戳正确性。
    WebRTC 还有其他很多丢帧逻辑,若网络正常且有持续有接收数据,但是视频卡顿或黑屏无显示,多为流本身的问题。

    Ending

    本文通过分析 WebRTC 音视频接收端的处理逻辑,列举了一些可以优化首帧显示的点,比如通过调整 local SDP 和 remote SDP 中与影响接收端处理的相关部分,从而避免 Audio/Video ReceiveStream 的重启。另外列举了 Android 解码器对视频宽高的要求、服务端对关键帧请求处理、以及 WebRTC 代码内部的一些丢帧逻辑等多个方面对视频显示的影响。 这些点都提高了融云 SDK 视频首帧的显示时间,改善了用户体验。

    展开全文
  • 生成gif 性能优化

    2010-01-15 15:01:00
    之前是使用的开源gif编码平均每次生成一个20的gif图片,平均时间大概是160ms,但因为是打规模生成,所以觉得还有提升空间,所以不断在网上找资料.于是找到octree算法.于是就拿了下来.gif算法,其实分两大部分.1,因为gif...

    因为工作原因.最近要对gif生成动画编码做优化.之前是使用的开源gif编码平均每次生成一个20帧的gif图片,平均时间大概是160ms,但因为是打规模生成,所以觉得还有提升空间,所以不断在网上找资料.于是找到octree算法.于是就拿了下来.

    gif算法,其实分两大部分.

    1,因为gif使用的是256色索引,所以要把16m色的图取出256个色,作为gif索引表.而octree(八叉树)取色算法是一种比较好的算法

    2.把生成的256色索引值变成编码成为lzw编码.

    在实际测试中,lzw算法是比较快的.所以,优化性能不大

    所以就对八叉树做优化了.

    一开始,参考了http://www.codeguru.com/cpp/g-m/gdi/gdi/article.php/c3677/这个算法.高出了一个基本编码器了.

    一测,发现比原来的算法大概快了一倍.大概80ms左右,

    因看到算法还有一定优化空间,于是继续进一步优化,首先,第一步就是把所有new的内存分配变成自己来分配.这里用了两种方法.一开始用了内存池.发现提高了10%,之后再一个栈分配.一次把所有内存都生成,而且中间没有del.这样,虽然占了一些内存,但是比原来提高了15%,可见在100000级的内存分配中.是比较显著的

    第二步,就是做一件简单的优化,例如把一些地方改成inline,一些递归的地方改成非递归.发现没有提升多少.,郁闷中

    第三布,总体编码,对所有帧统一编码,使用一个全局的colortable,这个方法的确快了20%左右,

    第四步,对相邻帧同相同颜色的优化,就是如果两帧是相同颜色,那就就一次过来统计.不过发现也提升不了很多.估计是内存块之间的切换有问题

    第五步,搞了一个多星期,还是没有提升多少,之后,决定自己参考重写一个octree算法,而且加入模板递归.测试了最后比较原来快了40左右,现在帧大概是50ms左右,我觉得暂时已经到了极限了.除非用汇编写了

     

     

    展开全文
  • 使用多张图片做动画的性能优化

    千次阅读 2017-11-08 00:26:15
    本文来自于Dev Club 开发者社区,背景QQ群的送礼物功能需要加载几十张图然后做动画,但是多张图片加载造成了非常大的性能开销,导致图片开始加载到真正播放动画的时间间隔比较长。所以需要研究一些优化方案提升...

    本文来自于Dev Club 开发者社区

    背景

    QQ群的送礼物功能需要加载几十张图然后做帧动画,但是多张图片加载造成了非常大的性能开销,导致图片开始加载到真正播放动画的时间间隔比较长。所以需要研究一些优化方案提升加载图片和帧动画的性能。

    原理分析

    iOS系统从磁盘加载一张图片,使用UIImageView显示到屏幕上,需要经过以下步骤:

    1. 从磁盘拷贝图片数据到内核缓冲区。

    2. 从内核缓冲区复制数据到用户空间。

    3. 生成UIImageView,把图像数据赋值给UIImageView。

    4. 如果图像数据为未解码的PNG/JPG,解码为位图数据。

    5. CATransaction捕获到UIImageView layer树的变化,主线程Runloop提交CATransaction,开始进行图像渲染。如果数据没有字节对齐,Core Animation会再拷贝一份数据,进行字节对齐。GPU处理位图数据,进行渲染。

    加载图片

    加载图片和图片解码是比较容易影响性能的一些因素。iOS设备上的闪存虽然是非常快的,但是还是要比内存慢接近200倍左右。图片加载的性能取决于CPU和IO,所以适当的减少IO次数可以提升一部分性能。

    通常情况下,应用应该在用户不会察觉的时候加载图片,可以针对情况采用预加载图片或者延迟加载。如果是帧动画这种情况,图片很难做延迟加载,因为帧动画的时间比较短,延迟加载很难保证播放每一帧时能提前加载到图片。有些比如列表滑动之类的情况延迟加载就会比较合理,并且可以采用子线程异步之类的方法防止滑动卡顿。

    解码

    图片加载结束之后在被渲染到屏幕之前,如果是未解码的JPEG或者PNG格式,图片会先被解码为位图数据。经过实际的测试,图片解码通常要比图片加载耗费更多的时间。iOS默认会在主线程对图像进行解码。很多库都解决了图像解码的问题,不过由于解码后的图像太大,一般不会缓存到磁盘,SDWebImage的做法是把解码操作从主线程移到子线程,让耗时的解码操作不占用主线程的时间。

    解码与加载图片耗时对比

    测试机型:iPhone 6+

    ---只加载不解码平均时长加载和解码平均时长
    同一张图加载三十次0.0008585310.005955906
    加载三十张不同的图0.0028288710.015458194

    解码的时间通常是加载图像数据时间的三到四倍左右。

    各种加载图片API的解码的时机

    UIKit:

    +imageNamed://加载到原图后立刻进行解码
    +imageWithContentsOfFile://图像渲染前进行解码

    UIImageView的image被赋值时会立刻进行解码。

    图像用UIKit内的绘图API绘制时会立刻进行解码,这个API的好处是可以在子线程进行。

    ImageIO:

    NSURL *imageURL = [NSURL fileURLWithPath:str];
    NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: @YES, (__bridge id)kCGImageSourceShouldCacheImmediately: @NO};
    CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,(__bridge CFDictionaryRef)options);
    UIImage *image = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CFRelease(source);

    kCGImageSourceShouldCacheImmediately 决定是否会在加载完后立刻开始解码。

    缓存

    缓存分为原图像的缓存和解码后位图数据的缓存。
    一般情况下,解码后的位图数据的缓存会跟随着原图UIImage,并且UIImage被拷贝后,指针变了之后图像需要重新去解码。

    解码后的位图数据可以通过以下的API获取。

    1.CGImageSourceCreateWithData(data) 创建 ImageSource。
    2.CGImageSourceCreateImageAtIndex(source) 创建一个未解码的 CGImage3.CGImageGetDataProvider(image) 获取这个图片的数据源。
    4.CGDataProviderCopyData(provider) 从数据源获取直接解码的数据。
    
    各种加载图片API的缓存策略

    UIKit:

    +imageNamed://原图和解码后的位图数据都会保存在系统缓存下,只有在内存低之类的时候才会被释放。所以这个API适合在应用多次使用的图片上使用。
    +imageWithContentsOfFile://不会对原图做缓存,只用一次的图片应该使用这个API。
    

    ImageIO:

    ImageIO的API的kCGImageSourceShouldCache选项决定了是否会对解码后的位图数据做缓存。64位设备上默认为开,32位设备上默认为关。
    

    任何时候,选取如何缓存总是一件比较难的事,正如菲尔 卡尔顿曾经说过:“在计算机科学中只有两件难事:缓存和命名”。

    解码后位图数据的缓存不好控制,我们一般也不会去操作它,但是原图像还是可以做一些缓存的。除了使用系统API来做缓存以外,应用也可以自定义一些缓存策略。或者可以使用NSCache。

    NSCache的API和NSDictionary很像,并且会根据所保存数据的使用频率,内存占用情况会适时的释放掉一其中一部分数据。

    图片格式

    常用的图片格式一般分为PNG和JPEG。

    对于PNG图片来说,加载会比JPEG更长,因为文件可能更大,但是解码会相对较快,而且Xcode会把PNG图片进行解码优化之后引入工程。JPEG图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为JPEG解压算法比基于zip的PNG算法更加复杂。

    JPEG 对于噪点大的图片效果比较好,PNG适合锋利的线条或者渐变色的图片。对于不友好的png图片,相同像素的JPEG图片总是比PNG加载更快,除非一些非常小的图片、但对于友好的PNG图片,一些中大尺寸的图效果还是很好的

    本文测试时使用的所有图片均为PNG格式。

    渲染

    关于图像的渲染,主要从以下三点分析:

    • offscreen rendring

    • Blending

    • Rasterize

    Offscreen rendering指的是在图像在绘制到当前屏幕前,需要先进行一次渲染,之后才绘制到当前屏幕。在进行offscreen rendring的时候,显卡需要另外alloc一块内存来进行渲染,渲染完毕后在绘制到当前屏幕,而且对于显卡来说,onscreen到offscreen的上下文环境切换是非常昂贵的(涉及到OpenGL的pipelines和barrier等),

    会造成offscreen rendring的操作有:

    • layer.mask 的使用

    • layer.maskToBounds 的使用

    • layer.allowsGroupOpacity 设置为yes 和 layer.opacity 小于1.0

    • layer.shouldRasterize 设置为yes

    • layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing

    Blending 会导致性能的损失。在iOS的图形处理中,Blending主要指的是混合像素颜色的计算。最直观的例子就是,我们把两个图层叠加在一起,如果第一个图层的透明的,则最终像素的颜色计算需要将第二个图层也考虑进来。这一过程即为Blending。更多的计算,导致性能的损失,在一些不需要透明度的地方,可以设置alpha为1.0 或者减少图层的叠加。

    Rasterize启用shouldRasterize属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的contents和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。
    当我们使用得当时,光栅化可以提供很大的性能优势但是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而且会让性能变的更糟。

    优化点

    加载

    应用可以通过减少IO次数来优化图像加载性能。这时候就可以使用精灵序列来把多张小图合成一张大图。 这样就能极大的减少IO次数了。

    因为IOS设备的限制,大图的大小不能超过2048 2048,有部分设备上这个限制可以达到4096 4096,但我们这里以小的为准。所以一张大图内最多只能容纳10张测试用的小图。

    精灵序列这种技术在cocos2d-x等平台上使用的很多。把多张小图拼成一张大图时可以使用TexturePacker软件来完成。

    精灵序列

    <img src="http://ww2.sinaimg.cn/mw690/6... height="291" width="258.3"/>

    简单来说精灵序列就是把多张小图拼成一张大图,附加一份plist文件保存着每一张小图对应在大图上的位置。然后iOS上就可以通过如下代码在不同小图之间切换。

    layer.contents = (id)img_.CGImage;//img为对应的大图
    layer.contentsRect = CGRectMake(0.1, 0.1, 0.2, 0.2);//contentsRect就是对应大图上小图的位置,更改这个值就可以在不同的小图间切换了。
    
    精灵序列与普通帧动画的性能对比

    下面的性能对比是通过20张小图和小图拼接而成的2张大图完成一组动画的对比数据。 测试机型是iPhone4s.

    1.文件大小

    小图总大小大图总大小
    758K827K

    因为大图内很难平铺所有小图,所以大图内很容易有空白像素,这就导致拼接后大图的总分辨率很容易超过所有小图的总分辨率,也就造成大图的体积会比所有小图的体积大。但是也有大图的体积小于所有小图总体积的情况,因为TexturePacker在拼接的时候会把小图内空白的像素适当的做点裁剪,然后把这个偏移值保存在plist文件内。

    2.加载速度

    小图的加载时间大图的加载时间
    ≈25ms≈5ms

    一张大图能容纳10张小图,极大的减小了IO数量,所以加载速度能大大提升。

    3.解码时间

    小图的解码时间大图的解码时间
    ≈35ms≈40ms

    因为大图的数据量比较大,所以大图的总体解码时间要比小图的解码时间长。

    4.CPU占用(CPU占用是通过同时执行两种动画,然后分别计算出两种动画的CPU占用率)

    小图方案的CPU占用率大图方案的CPU占用率
    ≈8% 峰值比较高≈20% 峰值比较低

    占用CPU最多的一个是从本地加载数据,另一个是图片解码。因为大图的IO次数比较少,所以加载大图时的CPU占用率要比加载所有小图时的低,但是大图的解码和两张大图切换时比较耗费CPU。总体来说大图这种方案的CPU占用率要高一点。

    5.内存占用

    小图方案的内存占用大图方案的内存占用
    ≈27MB≈30MB

    6.帧率

    小图方案的帧率大图方案的帧率
    ≈7fps≈7fps

    大图方案和小图方案的帧率基本一致。

    精灵序列总结

    总体来说精灵序列这种方案能明显减小图片开始加载到动画开始播放的延迟。但是精灵序列的文件大小容易变大,并且CPU占用率也要高一点。

    --文件大小加载速度解码时间CPU占用内存占用
    通过大图实现的精灵序列
    通过小图实现的普通帧动画

    精灵序列这种方案或许也可以直接用解码后的数据作为原图数据,这样虽然会让内存占用更高,文件大小进一步加大,但是能降低解码时间和CPU占用率。这个可以作为一个优化点继续研究一下。

    精灵序列的风险点:

    1. 因为一张大图最多能容纳10张小图,所以在两张大图切换的时候会造成CPU占用变高,这样就会有造成卡顿的风险。但是在iPhone4s上的测试下基本没有出现过卡顿。

    2. 拼接后的大图的文件大小容易变大,如果大图通过网络下载时耗时会变长。

    延迟解码

    根据上文的原理分析,针对多图帧动画,应用可以将图片解码延迟到图片渲染前,不要让图片加载后立马开始解码。这样也能降低图片加载到动画开始播放的延迟。

    缓存

    因为QQ群的送礼物功能中同一副帧动画重复播放的频率并不高,所以不需要考虑对原图或者对解码后位图数据做缓存。

    渲染

    渲染图形方面应用只能在将需要绘制的内容提交给GPU前做一些优化。针对帧动画这种场景,我们可以通过保证图像素材做到像素对其,尽量减少透明像素来做一些优化。

    如果使用精灵序列来做帧动画,应用必须通过CALayer进行渲染。播放帧动画时可以用NSTimer也可以使用CADisplayLink来不断的刷新图像数据。

    如果不使用精灵序列,应用也可以通过自定义一个UIImageView,自己通过CADisplayLink或者NSTimer实现帧动画,这样的话应用就可以自由控制图像解码的时间,图像数据的缓存等。

    总结

    本文通过对图片加载,多图做帧动画进行了一些原理分析,并且给出了一些优化点。也简单介绍了一下用精灵序列做帧动画的方案。除了QQ群送礼物的功能外其他通过图片做帧动画的功能也可以针对具体的业务情况选取其中的一些优化点进行一些优化。


    展开全文
  • 使用多张图片做动画的性能优化背景QQ群的送礼物功能需要加载几十张图然后做动画,但是多张图片加载造成了非常大的性能开销,导致图片开始加载到真正播放动画的时间间隔比较长。所以需要研究一些优化方案提升加载...

    使用多张图片做帧动画的性能优化

    背景

    QQ群的送礼物功能需要加载几十张图然后做帧动画,但是多张图片加载造成了非常大的性能开销,导致图片开始加载到真正播放动画的时间间隔比较长。所以需要研究一些优化方案提升加载图片和帧动画的性能。

    原理分析

    iOS系统从磁盘加载一张图片,使用UIImageView显示到屏幕上,需要经过以下步骤:

    从磁盘拷贝图片数据到内核缓冲区。

    从内核缓冲区复制数据到用户空间。

    生成UIImageView,把图像数据赋值给UIImageView。

    如果图像数据为未解码的PNG/JPG,解码为位图数据。

    CATransaction捕获到UIImageView layer树的变化,主线程Runloop提交CATransaction,开始进行图像渲染。如果数据没有字节对齐,Core Animation会再拷贝一份数据,进行字节对齐。GPU处理位图数据,进行渲染。

    加载图片

    加载图片和图片解码是比较容易影响性能的一些因素。iOS设备上的闪存虽然是非常快的,但是还是要比内存慢接近200倍左右。图片加载的性能取决于CPU和IO,所以适当的减少IO次数可以提升一部分性能。

    通常情况下,应用应该在用户不会察觉的时候加载图片,可以针对情况采用预加载图片或者延迟加载。如果是帧动画这种情况,图片很难做延迟加载,因为帧动画的时间比较短,延迟加载很难保证播放每一帧时能提前加载到图片。有些比如列表滑动之类的情况延迟加载就会比较合理,并且可以采用子线程异步之类的方法防止滑动卡顿。

    解码

    图片加载结束之后在被渲染到屏幕之前,如果是未解码的JPEG或者PNG格式,图片会先被解码为位图数据。经过实际的测试,图片解码通常要比图片加载耗费更多的时间。iOS默认会在主线程对图像进行解码。很多库都解决了图像解码的问题,不过由于解码后的图像太大,一般不会缓存到磁盘,SDWebImage的做法是把解码操作从主线程移到子线程,让耗时的解码操作不占用主线程的时间。

    解码与加载图片耗时对比

    测试机型:iPhone 6+

    ---

    只加载不解码平均时长

    加载和解码平均时长

    同一张图加载三十次

    0.000858531

    0.005955906

    加载三十张不同的图

    0.002828871

    0.015458194

    解码的时间通常是加载图像数据时间的三到四倍左右。

    各种加载图片API的解码的时机

    UIKit:

    +imageNamed://加载到原图后立刻进行解码

    +imageWithContentsOfFile://图像渲染前进行解码

    UIImageView的image被赋值时会立刻进行解码。

    图像用UIKit内的绘图API绘制时会立刻进行解码,这个API的好处是可以在子线程进行。

    ImageIO:

    NSURL *imageURL = [NSURL fileURLWithPath:str];

    NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: @YES, (__bridge id)kCGImageSourceShouldCacheImmediately: @NO};

    CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL);

    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,(__bridge CFDictionaryRef)options);

    UIImage *image = [UIImage imageWithCGImage:imageRef];

    CGImageRelease(imageRef);

    CFRelease(source);

    kCGImageSourceShouldCacheImmediately 决定是否会在加载完后立刻开始解码。

    缓存

    缓存分为原图像的缓存和解码后位图数据的缓存。

    一般情况下,解码后的位图数据的缓存会跟随着原图UIImage,并且UIImage被拷贝后,指针变了之后图像需要重新去解码。

    解码后的位图数据可以通过以下的API获取。

    1.CGImageSourceCreateWithData(data) 创建 ImageSource。

    2.CGImageSourceCreateImageAtIndex(source) 创建一个未解码的 CGImage。

    3.CGImageGetDataProvider(image) 获取这个图片的数据源。

    4.CGDataProviderCopyData(provider) 从数据源获取直接解码的数据。

    各种加载图片API的缓存策略

    UIKit:

    +imageNamed://原图和解码后的位图数据都会保存在系统缓存下,只有在内存低之类的时候才会被释放。所以这个API适合在应用多次使用的图片上使用。

    +imageWithContentsOfFile://不会对原图做缓存,只用一次的图片应该使用这个API。

    ImageIO:

    ImageIO的API的kCGImageSourceShouldCache选项决定了是否会对解码后的位图数据做缓存。64位设备上默认为开,32位设备上默认为关。

    任何时候,选取如何缓存总是一件比较难的事,正如菲尔 卡尔顿曾经说过:“在计算机科学中只有两件难事:缓存和命名”。

    解码后位图数据的缓存不好控制,我们一般也不会去操作它,但是原图像还是可以做一些缓存的。除了使用系统API来做缓存以外,应用也可以自定义一些缓存策略。或者可以使用NSCache。

    NSCache的API和NSDictionary很像,并且会根据所保存数据的使用频率,内存占用情况会适时的释放掉一其中一部分数据。

    图片格式

    常用的图片格式一般分为PNG和JPEG。

    对于PNG图片来说,加载会比JPEG更长,因为文件可能更大,但是解码会相对较快,而且Xcode会把PNG图片进行解码优化之后引入工程。JPEG图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为JPEG解压算法比基于zip的PNG算法更加复杂。

    JPEG 对于噪点大的图片效果比较好,PNG适合锋利的线条或者渐变色的图片。对于不友好的png图片,相同像素的JPEG图片总是比PNG加载更快,除非一些非常小的图片、但对于友好的PNG图片,一些中大尺寸的图效果还是很好的

    本文测试时使用的所有图片均为PNG格式。

    渲染

    关于图像的渲染,主要从以下三点分析:

    offscreen rendring

    Blending

    Rasterize

    Offscreen rendering指的是在图像在绘制到当前屏幕前,需要先进行一次渲染,之后才绘制到当前屏幕。在进行offscreen rendring的时候,显卡需要另外alloc一块内存来进行渲染,渲染完毕后在绘制到当前屏幕,而且对于显卡来说,onscreen到offscreen的上下文环境切换是非常昂贵的(涉及到OpenGL的pipelines和barrier等),

    会造成offscreen rendring的操作有:

    layer.mask 的使用

    layer.maskToBounds 的使用

    layer.allowsGroupOpacity 设置为yes 和 layer.opacity 小于1.0

    layer.shouldRasterize 设置为yes

    layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing

    Blending 会导致性能的损失。在iOS的图形处理中,Blending主要指的是混合像素颜色的计算。最直观的例子就是,我们把两个图层叠加在一起,如果第一个图层的透明的,则最终像素的颜色计算需要将第二个图层也考虑进来。这一过程即为Blending。更多的计算,导致性能的损失,在一些不需要透明度的地方,可以设置alpha为1.0 或者减少图层的叠加。

    Rasterize启用shouldRasterize属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的contents和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。

    当我们使用得当时,光栅化可以提供很大的性能优势但是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而且会让性能变的更糟。

    优化点

    加载

    应用可以通过减少IO次数来优化图像加载性能。这时候就可以使用精灵序列来把多张小图合成一张大图。 这样就能极大的减少IO次数了。

    因为IOS设备的限制,大图的大小不能超过2048 2048,有部分设备上这个限制可以达到4096 4096,但我们这里以小的为准。所以一张大图内最多只能容纳10张测试用的小图。

    精灵序列这种技术在cocos2d-x等平台上使用的很多。把多张小图拼成一张大图时可以使用TexturePacker软件来完成。

    精灵序列

    6...%20height=

    简单来说精灵序列就是把多张小图拼成一张大图,附加一份plist文件保存着每一张小图对应在大图上的位置。然后iOS上就可以通过如下代码在不同小图之间切换。

    layer.contents = (id)img_.CGImage;//img为对应的大图

    layer.contentsRect = CGRectMake(0.1, 0.1, 0.2, 0.2);//contentsRect就是对应大图上小图的位置,更改这个值就可以在不同的小图间切换了。

    精灵序列与普通帧动画的性能对比

    下面的性能对比是通过20张小图和小图拼接而成的2张大图完成一组动画的对比数据。 测试机型是iPhone4s.

    1.文件大小

    小图总大小

    大图总大小

    758K

    827K

    因为大图内很难平铺所有小图,所以大图内很容易有空白像素,这就导致拼接后大图的总分辨率很容易超过所有小图的总分辨率,也就造成大图的体积会比所有小图的体积大。但是也有大图的体积小于所有小图总体积的情况,因为TexturePacker在拼接的时候会把小图内空白的像素适当的做点裁剪,然后把这个偏移值保存在plist文件内。

    2.加载速度

    小图的加载时间

    大图的加载时间

    ≈25ms

    ≈5ms

    一张大图能容纳10张小图,极大的减小了IO数量,所以加载速度能大大提升。

    3.解码时间

    小图的解码时间

    大图的解码时间

    ≈35ms

    ≈40ms

    因为大图的数据量比较大,所以大图的总体解码时间要比小图的解码时间长。

    4.CPU占用(CPU占用是通过同时执行两种动画,然后分别计算出两种动画的CPU占用率)

    小图方案的CPU占用率

    大图方案的CPU占用率

    ≈8% 峰值比较高

    ≈20% 峰值比较低

    占用CPU最多的一个是从本地加载数据,另一个是图片解码。因为大图的IO次数比较少,所以加载大图时的CPU占用率要比加载所有小图时的低,但是大图的解码和两张大图切换时比较耗费CPU。总体来说大图这种方案的CPU占用率要高一点。

    5.内存占用

    小图方案的内存占用

    大图方案的内存占用

    ≈27MB

    ≈30MB

    6.帧率

    小图方案的帧率

    大图方案的帧率

    ≈7fps

    ≈7fps

    大图方案和小图方案的帧率基本一致。

    精灵序列总结

    总体来说精灵序列这种方案能明显减小图片开始加载到动画开始播放的延迟。但是精灵序列的文件大小容易变大,并且CPU占用率也要高一点。

    --

    文件大小

    加载速度

    解码时间

    CPU占用

    内存占用

    通过大图实现的精灵序列

    通过小图实现的普通帧动画

    精灵序列这种方案或许也可以直接用解码后的数据作为原图数据,这样虽然会让内存占用更高,文件大小进一步加大,但是能降低解码时间和CPU占用率。这个可以作为一个优化点继续研究一下。

    精灵序列的风险点:

    因为一张大图最多能容纳10张小图,所以在两张大图切换的时候会造成CPU占用变高,这样就会有造成卡顿的风险。但是在iPhone4s上的测试下基本没有出现过卡顿。

    拼接后的大图的文件大小容易变大,如果大图通过网络下载时耗时会变长。

    延迟解码

    根据上文的原理分析,针对多图帧动画,应用可以将图片解码延迟到图片渲染前,不要让图片加载后立马开始解码。这样也能降低图片加载到动画开始播放的延迟。

    缓存

    因为QQ群的送礼物功能中同一副帧动画重复播放的频率并不高,所以不需要考虑对原图或者对解码后位图数据做缓存。

    渲染

    渲染图形方面应用只能在将需要绘制的内容提交给GPU前做一些优化。针对帧动画这种场景,我们可以通过保证图像素材做到像素对其,尽量减少透明像素来做一些优化。

    如果使用精灵序列来做帧动画,应用必须通过CALayer进行渲染。播放帧动画时可以用NSTimer也可以使用CADisplayLink来不断的刷新图像数据。

    如果不使用精灵序列,应用也可以通过自定义一个UIImageView,自己通过CADisplayLink或者NSTimer实现帧动画,这样的话应用就可以自由控制图像解码的时间,图像数据的缓存等。

    总结

    本文通过对图片加载,多图做帧动画进行了一些原理分析,并且给出了一些优化点。也简单介绍了一下用精灵序列做帧动画的方案。除了QQ群送礼物的功能外其他通过图片做帧动画的功能也可以针对具体的业务情况选取其中的一些优化点进行一些优化。

    更多精彩内容欢迎关注腾讯优测的微信公众账号:

    1460000006831166?w=258&h=258

    腾讯优测是专业的移动云测试平台,为应用、游戏,H5混合应用的研发团队提供产品质量检测与问题解决服务。不仅在线上平台提供「全面兼容测试」、「云手机」等多种质量检测工具,同时在线下为VIP客户配备专家团队,提供定制化综合测试解决方案。真机实验室配备上千款手机,覆盖亿级用户,7*24小时在线运行,为各类测试工具提供支持。

    展开全文
  • 结束时间:不要记录首帧时间(windowsFocusChanged)。要记录第一个数据展示的时间 2 启动优化工具 2.1traceView 通过执行代码,使用文件的形式收集代码执行逻辑的信息。 生成文件在SD卡:Android/data/packagename/...
  • :动画、显示器如何显示图像、如何生成图、分层/分块/合成、js代码✍️码农手记将会邀请一直在幕后用代码和算法改变世界的技术大佬们将会不定期推送他们所写的在技术专业中的技术经验/研究/论文为你呈现/ 更前沿...
  • CPU通过图形API向GPU发送渲染指令,再通过硬件驱动...如果跟不上,或者CPU花费太多时间生成指令,速率开始下降。GPU前端前端是指CPU在渲染过程中处理顶点数据的部分。从CPU接收网格数据并发出drawCall -> 收集...
  • ( A )A、硬切换B、软件换C、接力切换D、前三项都有2、CDS测试生成的LOG后缀名是?( B)A、*.txtB、*.logC、*.aptD、*.acu3、PING包命令的PING请求时间间隔默认是多少?( B )A、100msB、1sC、5msD、10ms4、TD-LTE优化...
  • 为使DSP芯片有充裕的资源和时间用于复杂的导航计算、输出高频率的解算结果,通过资源优化,只采用FPGA逻辑电路实现了GPS信号的捕获、跟踪、同步、卫星自动搜索、伪距信息生成等基带处理功能,并整理了电文、历书、...
  • 前人种树后人乘凉原文地址 你需要了解什么是 在你的祖父辈,它们一般把视频称为”移动的图片”的一个原因是:视频中逼真的动作...它也就给了你以及UI系统大约16.67毫秒的时间,来完成生成一张静态图片()的所...
  • RN-性能优化 (三)

    2017-04-25 18:50:43
    前人种树后人乘凉 原文地址 你需要了解什么是 ...在你的祖父辈,它们一般把视频称为”移动的图片”的一个原因是:视频中逼真的动作是以...它也就给了你以及UI系统大约16.67毫秒的时间,来完成生成一张静态图片
  • 通过移动摄像机合成视频全景图,由于视频中存在时间冗余以及现有合成技术对大角度旋转存在失配,提出一种鲁棒性较好的视频全景并行合成方法。该方法首先对选取的关键采用并行线程提取可重复性特征,并利用特征估计...
  • 简介 RAIL模型是一种以用户为中心的性能模型。最终目标不是让网站在任何设备上都...Animation 动画:在10ms内生成 Idle 空闲:将推迟的工作分为50ms的块,利用空闲时间完成 Load 加载:在1000ms内呈现内容 加载...
  • 解决低延迟问题的核心思想...这样基本上保证在推流端出现网络抖动或者突然变差的情况下,能够舍弃已经缓存的buffer,继续推新生成好的视频。这样保证了,在网络端开始传输的时候的视频内容是最新的。 2. CDN nobuffe
  • 2.2.4 基于时间的因素 2.2.5 外部因素 2.3 可能的搜索引擎惩罚 2.3.1 Google沙盒效应 2.3.2 过期域名惩罚 2.3.3 重复内容惩罚 2.3.4 Google补充索引 2.4 资源和工具 2.4.1 Web分析器 2.4.2 市场研究 2.4.3 研究...
  • 对于人的每个视频序列,首先将空间卷积子网应用于每个以表示外观信息,然后将时间卷积子网链接到连续的较小范围,以提取局部运动信息。 这样的空间和时间卷积一起构成了我们基于T-CN的表示。 最后,利用递归...
  • 2.2.4 基于时间的因素 2.2.5 外部因素 2.3 可能的搜索引擎惩罚 2.3.1 Google沙盒效应 2.3.2 过期域名惩罚 2.3.3 重复内容惩罚 2.3.4 Google补充索引 2.4 资源和工具 2.4.1 Web分析器 2.4.2 市场研究 2.4.3 研究...
  • 第 11 章 网页动画与切片输出 本章要点 创建与编辑切片 创建动画 图像的优化与...三创建动画 PS 动画功能的发展 设置过渡 认识时间优化 GIF 动画 GIF 动画的生成 添加与删除 设置延迟时间 本章总结 处理网络
  • 渲染流水线与 CSSOM2.1 CSS 不会直接阻塞 DOM 构建2.2 CSS 会阻塞 JavaScript 执行2.3 白屏时间优化策略3. 分层与合成机制3.1 如何生成图像3.2 分层和合成:CSS动画比JavaScript高效3.3 分块3.4 利用分层技术...
  • python游戏开发实战:文本框(TextBox)发布时间:2018-...比如绘制文字的时候,每生成了surface等等问题,以后有空我再优化。当前代码修改时间:2018年11月1日 15:16:41import pygame class TextBox: def __init...
  • python游戏开发实战:文本框(TextBox)

    千次阅读 2018-11-01 15:17:45
    比如绘制文字的时候,每生成了surface等等问题,以后有空我再优化。 当前代码修改时间:2018年11月1日 15:16:41 import pygame class TextBox: def __init__(self, w, h, x, y, font=None, callback=None)...
  • EASY GIF Animator v5.2

    2019-03-21 15:08:56
    支持设置动画循环数和画持续时间 支持压缩单独动画画 支持图像转换 GIF 动画 支持生成 HTML 代码 支持调色板编辑 GIF 动画颜色 支持翻转 GIF 动画任意部份 支持输出 GIF 动画到 AVI 格式 支持 GIF, JPG, ...
  • fractal-garden-源码

    2021-03-20 23:00:26
    如果渲染器在生成框架时用完了时间,则可以渲染中间步骤之一。这意味着它优先考虑交互性而不是图像质量。 该项目还包含一个无头渲染服务器,该服务器可以将一组状态(每个状态都完全包含渲染图像所需的状态)...
  • 由于大多数计算都是使用当前游戏时间完成的,因此对于每次循环迭代,如果自上次更新以来经过的时间超过默认帧时间,则当前使用的游戏时间为最后游戏时间+默认帧时间的总和。 。 这保证了更好的整体一致性。 理想...
  • 姿势时间合并编码关键点时空上下文以生成有效的搜索范围,而姿势残差融合模块计算双向加权姿势残差。 然后通过我们的姿势校正网络对这些结果进行处理,以有效地优化姿势估计。 我们的方法在大型基准数据
  • 点击关注我们基于时空感知级联神经网络的视频前背景分离杨...接着,将当前乘以生成的二值化前景掩膜输入到二级背景重建子网络中,对前景缺失的视频进行高质量的修复重构.对于神经网络,选取合适的网络优化算法...
  • 场景图形的主要目的是改善场景优化,渲染状态排序和各种其它操作的性能,降低图形 渲染引擎的负荷,并实现复杂场景的“实时”渲染。实时渲染的目标是以足够高的速(符 合人眼的交互要求)渲染场景。飞行模拟程序...
  • 第一次打开估计会死一次(待优化),因为第一次会在data/data/cn.itcast.h264test下生成h264.3gp文件,该文件是视频流的范例文件,用于读取该手机录像生成的SPS和pps的值和位置,正式录像是以视频流一输出,...
  • 1. Elecard StreamEye Studio是一款功能强大专业的多媒体视频优化压缩编码工具,它可以该显示多媒体文件,它们的大小,类型,时间,位置和顺序流;比特率,以及其他常见的视频流参数。 2. 使用安装生成的Elecard ...
  • ffmpeg解码花屏问题

    千次阅读 2019-08-12 20:27:41
    最近在做一个视频分析相关的产品,基本架构就是使用ffmpeg取流,cuda解码,然后调用算法...况且,已经将将解码和取流分开,做了一级缓冲,再优化的空间实在不是很大,再加上时间紧,实在抽不出时间来解决该问题。 ...

空空如也

空空如也

1 2 3 4 5 6
收藏数 114
精华内容 45
关键字:

帧生成时间优化