1 /* 2 * Copyright (C) 2016 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 17 package com.android.tv.tuner.source; 18 19 import android.content.Context; 20 import android.util.Log; 21 import com.android.tv.tuner.data.TunerChannel; 22 import java.io.File; 23 import java.io.FileNotFoundException; 24 import java.io.FileOutputStream; 25 import java.io.IOException; 26 import java.util.HashSet; 27 import java.util.Set; 28 29 /** Stores TS files to the disk for debugging. */ 30 public class TsStreamWriter { 31 private static final String TAG = "TsStreamWriter"; 32 private static final boolean DEBUG = false; 33 34 private static final long TIME_LIMIT_MS = 10000; // 10s 35 private static final int NO_INSTANCE_ID = 0; 36 private static final int MAX_GET_ID_RETRY_COUNT = 5; 37 private static final int MAX_INSTANCE_ID = 10000; 38 private static final String SEPARATOR = "_"; 39 40 private FileOutputStream mFileOutputStream; 41 private long mFileStartTimeMs; 42 private String mFileName = null; 43 private final String mDirectoryPath; 44 private final File mDirectory; 45 private final int mInstanceId; 46 private TunerChannel mChannel; 47 TsStreamWriter(Context context)48 public TsStreamWriter(Context context) { 49 File externalFilesDir = context.getExternalFilesDir(null); 50 if (externalFilesDir == null || !externalFilesDir.isDirectory()) { 51 mDirectoryPath = null; 52 mDirectory = null; 53 mInstanceId = NO_INSTANCE_ID; 54 if (DEBUG) { 55 Log.w(TAG, "Fail to get external files dir!"); 56 } 57 } else { 58 mDirectoryPath = externalFilesDir.getPath() + "/EngTsStream"; 59 mDirectory = new File(mDirectoryPath); 60 if (!mDirectory.exists()) { 61 boolean madeDir = mDirectory.mkdir(); 62 if (!madeDir) { 63 Log.w(TAG, "Error. Fail to create folder!"); 64 } 65 } 66 mInstanceId = generateInstanceId(); 67 } 68 } 69 70 /** 71 * Sets the current channel. 72 * 73 * @param channel curren channel of the stream 74 */ setChannel(TunerChannel channel)75 public void setChannel(TunerChannel channel) { 76 mChannel = channel; 77 } 78 79 /** Opens a file to store TS data. */ openFile()80 public void openFile() { 81 if (mChannel == null || mDirectoryPath == null) { 82 return; 83 } 84 mFileStartTimeMs = System.currentTimeMillis(); 85 mFileName = 86 mChannel.getDisplayNumber() 87 + SEPARATOR 88 + mFileStartTimeMs 89 + SEPARATOR 90 + mInstanceId 91 + ".ts"; 92 String filePath = mDirectoryPath + "/" + mFileName; 93 try { 94 mFileOutputStream = new FileOutputStream(filePath, false); 95 } catch (FileNotFoundException e) { 96 Log.w(TAG, "Cannot open file: " + filePath, e); 97 } 98 } 99 100 /** 101 * Closes the file and stops storing TS data. 102 * 103 * @param calledWhenStopStream {@code true} if this method is called when the stream is stopped 104 * {@code false} otherwise 105 */ closeFile(boolean calledWhenStopStream)106 public void closeFile(boolean calledWhenStopStream) { 107 if (mFileOutputStream == null) { 108 return; 109 } 110 try { 111 mFileOutputStream.close(); 112 deleteOutdatedFiles(calledWhenStopStream); 113 mFileName = null; 114 mFileOutputStream = null; 115 } catch (IOException e) { 116 Log.w(TAG, "Error on closing file.", e); 117 } 118 } 119 120 /** 121 * Writes the data to the file. 122 * 123 * @param buffer the data to be written 124 * @param bytesWritten number of bytes written 125 */ writeToFile(byte[] buffer, int bytesWritten)126 public void writeToFile(byte[] buffer, int bytesWritten) { 127 if (mFileOutputStream == null) { 128 return; 129 } 130 if (System.currentTimeMillis() - mFileStartTimeMs > TIME_LIMIT_MS) { 131 closeFile(false); 132 openFile(); 133 } 134 try { 135 mFileOutputStream.write(buffer, 0, bytesWritten); 136 } catch (IOException e) { 137 Log.w(TAG, "Error on writing TS stream.", e); 138 } 139 } 140 141 /** 142 * Deletes outdated files to save storage. 143 * 144 * @param deleteAll {@code true} if all the files with the relative ID should be deleted {@code 145 * false} if the most recent file should not be deleted 146 */ deleteOutdatedFiles(boolean deleteAll)147 private void deleteOutdatedFiles(boolean deleteAll) { 148 if (mFileName == null) { 149 return; 150 } 151 if (mDirectory == null || !mDirectory.isDirectory()) { 152 Log.e(TAG, "Error. The folder doesn't exist!"); 153 return; 154 } 155 if (mFileName == null) { 156 Log.e(TAG, "Error. The current file name is null!"); 157 return; 158 } 159 for (File file : mDirectory.listFiles()) { 160 if (file.isFile() 161 && getFileId(file) == mInstanceId 162 && (deleteAll || !mFileName.equals(file.getName()))) { 163 boolean deleted = file.delete(); 164 if (DEBUG && !deleted) { 165 Log.w(TAG, "Failed to delete " + file.getName()); 166 } 167 } 168 } 169 } 170 171 /** 172 * Generates a unique instance ID. 173 * 174 * @return a unique instance ID 175 */ generateInstanceId()176 private int generateInstanceId() { 177 if (mDirectory == null) { 178 return NO_INSTANCE_ID; 179 } 180 Set<Integer> idSet = getExistingIds(); 181 if (idSet == null) { 182 return NO_INSTANCE_ID; 183 } 184 for (int i = 0; i < MAX_GET_ID_RETRY_COUNT; i++) { 185 // Range [1, MAX_INSTANCE_ID] 186 int id = (int) Math.floor(Math.random() * MAX_INSTANCE_ID) + 1; 187 if (!idSet.contains(id)) { 188 return id; 189 } 190 } 191 return NO_INSTANCE_ID; 192 } 193 194 /** 195 * Gets all existing instance IDs. 196 * 197 * @return a set of all existing instance IDs 198 */ getExistingIds()199 private Set<Integer> getExistingIds() { 200 if (mDirectory == null || !mDirectory.isDirectory()) { 201 return null; 202 } 203 204 Set<Integer> idSet = new HashSet<>(); 205 for (File file : mDirectory.listFiles()) { 206 int id = getFileId(file); 207 if (id != NO_INSTANCE_ID) { 208 idSet.add(id); 209 } 210 } 211 return idSet; 212 } 213 214 /** 215 * Gets the instance ID of a given file. 216 * 217 * @param file the file whose TsStreamWriter ID is returned 218 * @return the TsStreamWriter ID of the file or NO_INSTANCE_ID if not available 219 */ getFileId(File file)220 private static int getFileId(File file) { 221 if (file == null || !file.isFile()) { 222 return NO_INSTANCE_ID; 223 } 224 String fileName = file.getName(); 225 int lastSeparator = fileName.lastIndexOf(SEPARATOR); 226 if (!fileName.endsWith(".ts") || lastSeparator == -1) { 227 return NO_INSTANCE_ID; 228 } 229 try { 230 return Integer.parseInt(fileName.substring(lastSeparator + 1, fileName.length() - 3)); 231 } catch (NumberFormatException e) { 232 if (DEBUG) { 233 Log.e(TAG, fileName + " is not a valid file name."); 234 } 235 } 236 return NO_INSTANCE_ID; 237 } 238 } 239