1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.usbtuner.exoplayer.cache; 18 19import android.media.MediaFormat; 20import android.util.Pair; 21 22import java.io.DataInputStream; 23import java.io.DataOutputStream; 24import java.io.File; 25import java.io.FileInputStream; 26import java.io.FileOutputStream; 27import java.io.IOException; 28import java.nio.ByteBuffer; 29import java.nio.charset.StandardCharsets; 30import java.util.ArrayList; 31import java.util.SortedMap; 32 33/** 34 * Manages DVR storage. 35 */ 36public class DvrStorageManager implements CacheManager.StorageManager { 37 38 // TODO: make serializable classes and use protobuf after internal data structure is finalized. 39 private static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO = 40 "com.google.android.videos.pixelWidthHeightRatio"; 41 private static final String META_FILE_SUFFIX = ".meta"; 42 private static final String IDX_FILE_SUFFIX = ".idx"; 43 44 // Size of minimum reserved storage buffer which will be used to save meta files 45 // and index files after actual recording finished. 46 private static final long MIN_BUFFER_BYTES = 256L * 1024 * 1024; 47 private static final int NO_VALUE = -1; 48 private static final long NO_VALUE_LONG = -1L; 49 50 private final File mCacheDir; 51 52 // {@code true} when this is for recording, {@code false} when this is for replaying. 53 private final boolean mIsRecording; 54 55 public DvrStorageManager(File file, boolean isRecording) { 56 mCacheDir = file; 57 mCacheDir.mkdirs(); 58 mIsRecording = isRecording; 59 } 60 61 @Override 62 public void clearStorage() { 63 if (mIsRecording) { 64 for (File file : mCacheDir.listFiles()) { 65 file.delete(); 66 } 67 } 68 } 69 70 @Override 71 public File getCacheDir() { 72 return mCacheDir; 73 } 74 75 @Override 76 public boolean isPersistent() { 77 return true; 78 } 79 80 @Override 81 public boolean reachedStorageMax(long cacheSize, long pendingDelete) { 82 return false; 83 } 84 85 @Override 86 public boolean hasEnoughBuffer(long pendingDelete) { 87 return !mIsRecording || mCacheDir.getUsableSpace() >= MIN_BUFFER_BYTES; 88 } 89 90 private void readFormatInt(DataInputStream in, MediaFormat format, String key) 91 throws IOException { 92 int val = in.readInt(); 93 if (val != NO_VALUE) { 94 format.setInteger(key, val); 95 } 96 } 97 98 private void readFormatLong(DataInputStream in, MediaFormat format, String key) 99 throws IOException { 100 long val = in.readLong(); 101 if (val != NO_VALUE_LONG) { 102 format.setLong(key, val); 103 } 104 } 105 106 private void readFormatFloat(DataInputStream in, MediaFormat format, String key) 107 throws IOException { 108 float val = in.readFloat(); 109 if (val != NO_VALUE) { 110 format.setFloat(key, val); 111 } 112 } 113 114 private String readString(DataInputStream in) throws IOException { 115 int len = in.readInt(); 116 if (len <= 0) { 117 return null; 118 } 119 byte [] strBytes = new byte[len]; 120 in.readFully(strBytes); 121 return new String(strBytes, StandardCharsets.UTF_8); 122 } 123 124 private void readFormatString(DataInputStream in, MediaFormat format, String key) 125 throws IOException { 126 String str = readString(in); 127 if (str != null) { 128 format.setString(key, str); 129 } 130 } 131 132 private ByteBuffer readByteBuffer(DataInputStream in) throws IOException { 133 int len = in.readInt(); 134 if (len <= 0) { 135 return null; 136 } 137 byte [] bytes = new byte[len]; 138 in.readFully(bytes); 139 ByteBuffer buffer = ByteBuffer.allocate(len); 140 buffer.put(bytes); 141 buffer.flip(); 142 143 return buffer; 144 } 145 146 private void readFormatByteBuffer(DataInputStream in, MediaFormat format, String key) 147 throws IOException { 148 ByteBuffer buffer = readByteBuffer(in); 149 if (buffer != null) { 150 format.setByteBuffer(key, buffer); 151 } 152 } 153 154 @Override 155 public Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) throws IOException { 156 File file = new File(getCacheDir(), (isAudio ? "audio" : "video") + META_FILE_SUFFIX); 157 try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { 158 String name = readString(in); 159 MediaFormat format = new MediaFormat(); 160 readFormatString(in, format, MediaFormat.KEY_MIME); 161 readFormatInt(in, format, MediaFormat.KEY_MAX_INPUT_SIZE); 162 readFormatInt(in, format, MediaFormat.KEY_WIDTH); 163 readFormatInt(in, format, MediaFormat.KEY_HEIGHT); 164 readFormatInt(in, format, MediaFormat.KEY_CHANNEL_COUNT); 165 readFormatInt(in, format, MediaFormat.KEY_SAMPLE_RATE); 166 readFormatFloat(in, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); 167 for (int i = 0; i < 3; ++i) { 168 readFormatByteBuffer(in, format, "csd-" + i); 169 } 170 readFormatLong(in, format, MediaFormat.KEY_DURATION); 171 return new Pair<>(name, format); 172 } 173 } 174 175 @Override 176 public ArrayList<Long> readIndexFile(String trackId) throws IOException { 177 ArrayList<Long> indices = new ArrayList<>(); 178 File file = new File(getCacheDir(), trackId + IDX_FILE_SUFFIX); 179 try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { 180 long count = in.readLong(); 181 for (long i = 0; i < count; ++i) { 182 indices.add(in.readLong()); 183 } 184 return indices; 185 } 186 } 187 188 private void writeFormatInt(DataOutputStream out, MediaFormat format, String key) 189 throws IOException { 190 if (format.containsKey(key)) { 191 out.writeInt(format.getInteger(key)); 192 } else { 193 out.writeInt(NO_VALUE); 194 } 195 } 196 197 private void writeFormatLong(DataOutputStream out, MediaFormat format, String key) 198 throws IOException { 199 if (format.containsKey(key)) { 200 out.writeLong(format.getLong(key)); 201 } else { 202 out.writeLong(NO_VALUE_LONG); 203 } 204 } 205 206 private void writeFormatFloat(DataOutputStream out, MediaFormat format, String key) 207 throws IOException { 208 if (format.containsKey(key)) { 209 out.writeFloat(format.getFloat(key)); 210 } else { 211 out.writeFloat(NO_VALUE); 212 } 213 } 214 215 private void writeString(DataOutputStream out, String str) throws IOException { 216 byte [] data = str.getBytes(StandardCharsets.UTF_8); 217 out.writeInt(data.length); 218 if (data.length > 0) { 219 out.write(data); 220 } 221 } 222 223 private void writeFormatString(DataOutputStream out, MediaFormat format, String key) 224 throws IOException { 225 if (format.containsKey(key)) { 226 writeString(out, format.getString(key)); 227 } else { 228 out.writeInt(0); 229 } 230 } 231 232 private void writeByteBuffer(DataOutputStream out, ByteBuffer buffer) throws IOException { 233 byte [] data = new byte[buffer.limit()]; 234 buffer.get(data); 235 buffer.flip(); 236 out.writeInt(data.length); 237 if (data.length > 0) { 238 out.write(data); 239 } else { 240 out.writeInt(0); 241 } 242 } 243 244 private void writeFormatByteBuffer(DataOutputStream out, MediaFormat format, String key) 245 throws IOException { 246 if (format.containsKey(key)) { 247 writeByteBuffer(out, format.getByteBuffer(key)); 248 } else { 249 out.writeInt(0); 250 } 251 } 252 253 @Override 254 public void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio) 255 throws IOException { 256 File file = new File(getCacheDir(), (isAudio ? "audio" : "video") + META_FILE_SUFFIX); 257 try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { 258 writeString(out, trackId); 259 writeFormatString(out, format, MediaFormat.KEY_MIME); 260 writeFormatInt(out, format, MediaFormat.KEY_MAX_INPUT_SIZE); 261 writeFormatInt(out, format, MediaFormat.KEY_WIDTH); 262 writeFormatInt(out, format, MediaFormat.KEY_HEIGHT); 263 writeFormatInt(out, format, MediaFormat.KEY_CHANNEL_COUNT); 264 writeFormatInt(out, format, MediaFormat.KEY_SAMPLE_RATE); 265 writeFormatFloat(out, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); 266 for (int i = 0; i < 3; ++i) { 267 writeFormatByteBuffer(out, format, "csd-" + i); 268 } 269 writeFormatLong(out, format, MediaFormat.KEY_DURATION); 270 } 271 } 272 273 @Override 274 public void writeIndexFile(String trackName, SortedMap<Long, SampleCache> index) 275 throws IOException { 276 File indexFile = new File(getCacheDir(), trackName + IDX_FILE_SUFFIX); 277 try (DataOutputStream out = new DataOutputStream(new FileOutputStream(indexFile))) { 278 out.writeLong(index.size()); 279 for (Long key : index.keySet()) { 280 out.writeLong(key); 281 } 282 } 283 } 284} 285