media cache 对比.

对工作中用到的media player 播放领域中遇到的三个cache 实现优缺点总结:

1,android 原生的NuCacheSource2

2,嵌入式播放中常用的ringBuffer cache

3,youtobe 开源播放器exoplayer 中用到的cache 策略

 

I.android 原生中http + mp4 的播放模式,为缓解网络抖动平衡下载的时机,开发了NuCacheSource2 

NuCacheSource2 集成与DataSource 抽象类,提供缓存读写统计功能

 

android 的多媒体框架在Source端的模型如图:

1,DataSource 被抽象为数据源(例如HTTP,Cache,LocalFIle,Prefetching) 抽象的是一种访问数据的类型.可以用IBINDER 初始化,也可以无参初始化,非常灵活.    

40class DataSource : public RefBase {
41public:
42    enum Flags {
43        kWantsPrefetching      = 1,
44        kStreamedFromLocalHost = 2,
45        kIsCachingDataSource   = 4,
46        kIsHTTPBasedSource     = 8,
47    };
48
49    static sp<DataSource> CreateFromURI(
50            const sp<IMediaHTTPService> &httpService,
51            const char *uri,
52            const KeyedVector<String8, String8> *headers = NULL,
53            String8 *contentType = NULL,
54            HTTPBase *httpSource = NULL);
55
56    static sp<DataSource> CreateMediaHTTP(const sp<IMediaHTTPService> &httpService);
57    static sp<DataSource> CreateFromIDataSource(const sp<IDataSource> &source);
58
59    DataSource() {}
60
61    virtual status_t initCheck() const = 0;
62
63    // Returns the number of bytes read, or -1 on failure. It's not an error if
64    // this returns zero; it just means the given offset is equal to, or
65    // beyond, the end of the source.
66    virtual ssize_t readAt(off64_t offset, void *data, size_t size) = 0;
67
74    // May return ERROR_UNSUPPORTED.
75    virtual status_t getSize(off64_t *size);
76
77    virtual uint32_t flags() {
78        return 0;
79    }
80
81    virtual status_t reconnectAtOffset(off64_t offset) {
82        return ERROR_UNSUPPORTED;
83    }
84

104    virtual String8 getUri() {
105        return String8();
106    }
107
108    virtual String8 getMIMEType() const;

 

2,NuPlayer::Source 是MediaSource 的抽象代表一种多媒体源以及对多媒体源的基本的操作行为(prepare,start,pause,resume,seek,disconnect)

struct NuPlayer::Source : public AHandler {
34    enum Flags {
35        FLAG_CAN_PAUSE          = 1,
36        FLAG_CAN_SEEK_BACKWARD  = 2,  // the "10 sec back button"
37        FLAG_CAN_SEEK_FORWARD   = 4,  // the "10 sec forward button"
38        FLAG_CAN_SEEK           = 8,  // the "seek bar"
39        FLAG_DYNAMIC_DURATION   = 16,
40        FLAG_SECURE             = 32,
41        FLAG_PROTECTED          = 64,
42    };
43
44    enum {
45        kWhatPrepared,
46        kWhatFlagsChanged,
47        kWhatVideoSizeChanged,
48        kWhatBufferingUpdate,
49        kWhatBufferingStart,
50        kWhatBufferingEnd,
51        kWhatPauseOnBufferingStart,
52        kWhatResumeOnBufferingEnd,
53        kWhatCacheStats,
54        kWhatSubtitleData,
55        kWhatTimedTextData,
56        kWhatTimedMetaData,
57        kWhatQueueDecoderShutdown,
58        kWhatDrmNoLicense,
59        kWhatInstantiateSecureDecoders,
60    };
61
62    // The provides message is used to notify the player about various
63    // events.
64    Source(const sp<AMessage> &notify)
65        : mNotify(notify) {
66    }
67
68    virtual void prepareAsync() = 0;
69
70    virtual void start() = 0;
71    virtual void stop() {}
72    virtual void pause() {}
73    virtual void resume() {}
74
75    // Explicitly disconnect the underling data source
76    virtual void disconnect() {}
77
78    // Returns OK iff more data was available,
79    // an error or ERROR_END_OF_STREAM if not.
80    virtual status_t feedMoreTSData() = 0;
81
82    virtual sp<AMessage> getFormat(bool audio);
83    virtual sp<MetaData> getFormatMeta(bool /* audio */) { return NULL; }
84    virtual sp<MetaData> getFileFormatMeta() const { return NULL; }
85
86    virtual status_t dequeueAccessUnit(
87            bool audio, sp<ABuffer> *accessUnit) = 0;
88
89    virtual status_t getDuration(int64_t * /* durationUs */) {
90        return INVALID_OPERATION;
91    }
92
93    virtual size_t getTrackCount() const {
94        return 0;
95    }
96
97    virtual sp<AMessage> getTrackInfo(size_t /* trackIndex */) const {
98        return NULL;
99    }
100
101    virtual ssize_t getSelectedTrack(media_track_type /* type */) const {
102        return INVALID_OPERATION;
103    }
104
105    virtual status_t selectTrack(size_t /* trackIndex */, bool /* select */, int64_t /* timeUs*/) {
106        return INVALID_OPERATION;
107    }
108
109    virtual status_t seekTo(int64_t /* seekTimeUs */) {
110        return INVALID_OPERATION;
111    }
112
113    virtual status_t setBuffers(bool /* audio */, Vector<MediaBuffer *> &/* buffers */) {
114        return INVALID_OPERATION;
115    }
116
117    virtual bool isRealTime() const {
118        return false;
119    }
120
121    virtual bool isStreaming() const {
122        return true;
123    }

 

3,这个类重点提供两个方法fetch,read 通过PageCache.

30struct NuCachedSource2 : public DataSource {
31    static sp<NuCachedSource2> Create(
32            const sp<DataSource> &source,
33            const char *cacheConfig = NULL,
34            bool disconnectAtHighwatermark = false);
35
36    virtual status_t initCheck() const;
38    virtual ssize_t readAt(off64_t offset, void *data, size_t size);
40    virtual void disconnect();
42    virtual status_t getSize(off64_t *size);
43    virtual uint32_t flags();
49    virtual String8 getMIMEType() const;
50
51    
52
53    size_t cachedSize();
54    size_t approxDataRemaining(status_t *finalStatus) const;
56    void resumeFetchingIfNecessary();
58    // The following methods are supported only if the
59    // data source is HTTP-based; otherwise, ERROR_UNSUPPORTED
60    // is returned.
61    status_t getEstimatedBandwidthKbps(int32_t *kbps);
62    status_t setCacheStatCollectFreq(int32_t freqMs);
64    static void RemoveCacheSpecificHeaders(
65            KeyedVector<String8, String8> *headers,
66            String8 *cacheConfig,
67            bool *disconnectAtHighwatermark);
68
69protected:
70    virtual ~NuCachedSource2();
71
72private:
73    friend struct AHandlerReflector<NuCachedSource2>;
75    NuCachedSource2(
76            const sp<DataSource> &source,
77            const char *cacheConfig,
78            bool disconnectAtHighwatermark);
80    enum {
81        kPageSize                       = 65536,
82        kDefaultHighWaterThreshold      = 20 * 1024 * 1024,
83        kDefaultLowWaterThreshold       = 4 * 1024 * 1024,
84
85        // Read data after a 15 sec timeout whether we're actively
86        // fetching or not.
87        kDefaultKeepAliveIntervalUs     = 15000000,
88    };
90    enum {
91        kWhatFetchMore  = 'fetc',
92        kWhatRead       = 'read',
93    };
95    enum {
96        kMaxNumRetries = 10,
97    };
99    sp<DataSource> mSource;
100    sp<AHandlerReflector<NuCachedSource2> > mReflector;
101    sp<ALooper> mLooper;
102
103    Mutex mSerializer;
104    mutable Mutex mLock;
105    Condition mCondition;
106
107    PageCache *mCache;
108    off64_t mCacheOffset;
109    status_t mFinalStatus;
110    off64_t mLastAccessPos;
111    sp<AMessage> mAsyncResult;
112    bool mFetching;
113    bool mDisconnecting;
114    int64_t mLastFetchTimeUs;
115
116    int32_t mNumRetriesLeft;
117
118    size_t mHighwaterThresholdBytes;
119    size_t mLowwaterThresholdBytes;
120
121    // If the keep-alive interval is 0, keep-alives are disabled.
122    int64_t mKeepAliveIntervalUs;
123
124    bool mDisconnectAtHighwatermark;
125
126    void onMessageReceived(const sp<AMessage> &msg);
127    void onFetch();
128    void onRead(const sp<AMessage> &msg);
129
130    void fetchInternal();
131    ssize_t readInternal(off64_t offset, void *data, size_t size);
132    status_t seekInternal_l(off64_t offset);
133
134    size_t approxDataRemaining_l(status_t *finalStatus) const;
135
136    void restartPrefetcherIfNecessary_l(
137            bool ignoreLowWaterThreshold = false, bool force = false);
138
139    void updateCacheParamsFromSystemProperty();
140    void updateCacheParamsFromString(const char *s);
141
142    DISALLOW_EVIL_CONSTRUCTORS(NuCachedSource2);
143};

详解一下NuCachedSource2,PageCache;

 PageCache 是一个数据结构,维护了两个List <page*>Activity ,Free ;Acivity 中的是need read 的数据,Free 中是已经读取解析需要释放的数据.数据结构简单有效,易于理解.确定是没有migration 容易产生页碎片.

NuCacheSource2 是一个数据缓冲策略类,如低水位fetching数据,高水位停止bufferring 数据,retryFecting ,AkeepLiveCache ;以及调用HTTPBase或CURL 搜集一些码率带宽cacheDuration 等统计信息.

NOTICE: PageCache 是给NuCacheSource2的成员变量DataSource (HTTPDataSource--------->OKHTTP) 填充数据用的

II , 嵌入式中常用的cache 数据结构是RingBuffer,如在android  中可以直接替代PageCache;具体就是规定readPointer 永远追赶writePointer,抽象出一个无限大的cache. 好处就是在处理高帧率高清晰度的片源时节省空间,缺点就是多线程访问比较麻烦,每次读之前都需要 readPointer合writePointer比较

//TODO

 

 

III ,EXOPLAYER + OKIO

    缓存分为两层:

                          1,okio 提供的丰富的source ,sink 访问pipe,async;共享buffer (类似与ArrayList 的结构;循环链表+segments);而Buffer 拥有的所有segment 都来自于segmentPool 管理的单链表. segments 提供 ownership 所以可以通过调用split方法改变ownership 以及share 减少copy;segments 提供compact 方法减少segments 碎片.

https://github.com/square/okio

Buffer is a mutable sequence of bytes. Like ArrayList, you don't need to size your buffer in advance. You read and write buffers as a queue: write data to the end and read it from the front. There's no obligation to manage positions, limits, or capacities.

Buffer is implemented as a linked list of segments. When you move data from one buffer to another, it reassigns ownershipof the segments rather than copying the data across. This approach is particularly helpful for multithreaded programs: a thread that talks to the network can exchange data with a worker thread without any copying or ceremony.

 

final class Segment {
 /** The size of all segments in bytes. */
 static final int SIZE = 8192;
  
 /** Segments will be shared when doing so avoids {@code arraycopy()} of this many bytes. */
 static final int SHARE_MINIMUM = 1024;
  
 final byte[] data;
  
 /** The next byte of application data byte to read in this segment. */
 int pos;
  
 /** The first byte of available data ready to be written to. */
 int limit;
  
 /** True if other segments or byte strings use the same byte array. */
 boolean shared;
  
 /** True if this segment owns the byte array and can append to it, extending {@code limit}. */
 boolean owner;
  
 /** Next segment in a linked or circularly-linked list. */
 Segment next;
  
 /** Previous segment in a circularly-linked list. */
 Segment prev;

 

在segment中有几个有意思的方法。

compact方法
  /**
   * Call this when the tail and its predecessor may both be less than half
   * full. This will copy data so that segments can be recycled.
   */
  public void compact() {
    if (prev == this) throw new IllegalStateException();
    if (!prev.owner) return; // Cannot compact: prev isn't writable.
    int byteCount = limit - pos;
    int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
    if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
    writeTo(prev, byteCount);
    pop();
    SegmentPool.recycle(this);
  }
  • 当Segment的前一个和自身的数据量都不足一半时,会对segement进行压缩,把自身的数据写入到前一个Segment中,然后将自身进行回收。
split

将一个Segment的数据拆成两个,注意,这里有trick。如果有两个Segment相同的字节超过了SHARE_MINIMUM (1024),那么这两个Segment会共享一份数据,这样就省去了开辟内存及复制内存的开销,达到了提高性能的目的。

public Segment split(int byteCount) {
    if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
    Segment prefix;

    // We have two competing performance goals:
    //  - Avoid copying data. We accomplish this by sharing segments.
    //  - Avoid short shared segments. These are bad for performance because they are readonly and
    //    may lead to long chains of short segments.
    // To balance these goals we only share segments when the copy will be large.
    if (byteCount >= SHARE_MINIMUM) {
      prefix = new Segment(this);
    } else {
      prefix = SegmentPool.take();
      System.arraycopy(data, pos, prefix.data, 0, byteCount);
    }

    prefix.limit = prefix.pos + byteCount;
    pos += byteCount;
    prev.push(prefix);
    return prefix;
  }
  •  
  • SegmentPool

这是一个回收池,目前的设计是能存放64K的字节,即8个Segment。在实际使用中,建议对其进行调整。

final class SegmentPool {
  /** The maximum number of bytes to pool. */
  // TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments?
  static final long MAX_SIZE = 64 * 1024; // 64 KiB.

  /** Singly-linked list of segments. */
  static Segment next;

  /** Total bytes in this pool. */
  static long byteCount;
    ...
}
  •  

讲到这里,整个Buffer的实现原理也就呼之欲出了。

Buffer的写操作,实际上就是不断增加Segment的一个过程,读操作,就是不断消耗Segment中的数据,如果数据读取完,则使用SegmentPool进行回收。 
当复制内存数据时,使用Segment的共享机制,多个Segment共享一份data[]。

Buffer更多的逻辑主要是跨Segment读取数据,需要把前一个Segment的尾端和后一个Segment的前端拼接在一起,因此看起来代码量相对多,但其实开销非常低。

TimeOut机制

在Okio中定义了一个类叫TimeOut,主要用于判断时间是否超过阈值,超过之后就抛出中断异常。

 public void throwIfReached() throws IOException {
    if (Thread.interrupted()) {
      throw new InterruptedIOException("thread interrupted");
    }

    if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {
      throw new InterruptedIOException("deadline reached");
    }
  } 
  • 本身是以有序链表的方式,按照超时的时间进行排序。在其head是一个占位的AsyncTime,主要用于启动WatchDog线程。这种异步超时主要可以用在当时间到时,就可以立即获得通知,不需要等待某阻塞方法返回时,才知道超时了。使用异步超时,timeout方法在发生超时会进行回调,需要重载timedOut()方法以处理超时事件

 

2,exo 内部是一个vector 初始化大小为16M, 有高水位限制内存的增长

高水位30s && %80*TotalCacheSize

低水位5s  or %20*TotalCacheSize

 

 

 

转载于:https://my.oschina.net/u/269082/blog/899350

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值