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 23 import java.io.File; 24 import java.io.FileNotFoundException; 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.util.HashSet; 28 import java.util.Set; 29 30 /** 31 * Stores TS files to the disk for debugging. 32 */ 33 public class TsStreamWriter { 34 private static final String TAG = "TsStreamWriter"; 35 private static final boolean DEBUG = false; 36 37 private static final long TIME_LIMIT_MS = 10000; // 10s 38 private static final int NO_INSTANCE_ID = 0; 39 private static final int MAX_GET_ID_RETRY_COUNT = 5; 40 private static final int MAX_INSTANCE_ID = 10000; 41 private static final String SEPARATOR = "_"; 42 43 private FileOutputStream mFileOutputStream; 44 private long mFileStartTimeMs; 45 private String mFileName = null; 46 private final String mDirectoryPath; 47 private final File mDirectory; 48 private final int mInstanceId; 49 private TunerChannel mChannel; 50 TsStreamWriter(Context context)51 public TsStreamWriter(Context context) { 52 File externalFilesDir = context.getExternalFilesDir(null); 53 if (externalFilesDir == null || !externalFilesDir.isDirectory()) { 54 mDirectoryPath = null; 55 mDirectory = null; 56 mInstanceId = NO_INSTANCE_ID; 57 if (DEBUG) { 58 Log.w(TAG, "Fail to get external files dir!"); 59 } 60 } else { 61 mDirectoryPath = externalFilesDir.getPath() + "/EngTsStream"; 62 mDirectory = new File(mDirectoryPath); 63 if (!mDirectory.exists()) { 64 boolean madeDir = mDirectory.mkdir(); 65 if (!madeDir) { 66 Log.w(TAG, "Error. Fail to create folder!"); 67 } 68 } 69 mInstanceId = generateInstanceId(); 70 } 71 } 72 73 /** 74 * Sets the current channel. 75 * 76 * @param channel curren channel of the stream 77 */ setChannel(TunerChannel channel)78 public void setChannel(TunerChannel channel) { 79 mChannel = channel; 80 } 81 82 /** 83 * Opens a file to store TS data. 84 */ openFile()85 public void openFile() { 86 if (mChannel == null || mDirectoryPath == null) { 87 return; 88 } 89 mFileStartTimeMs = System.currentTimeMillis(); 90 mFileName = mChannel.getDisplayNumber() + SEPARATOR + mFileStartTimeMs + SEPARATOR 91 + mInstanceId + ".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 145 * {@code 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() && getFileId(file) == mInstanceId 161 && (deleteAll || !mFileName.equals(file.getName()))) { 162 boolean deleted = file.delete(); 163 if (DEBUG && !deleted) { 164 Log.w(TAG, "Failed to delete " + file.getName()); 165 } 166 } 167 } 168 } 169 170 /** 171 * Generates a unique instance ID. 172 * 173 * @return a unique instance ID 174 */ generateInstanceId()175 private int generateInstanceId() { 176 if (mDirectory == null) { 177 return NO_INSTANCE_ID; 178 } 179 Set<Integer> idSet = getExistingIds(); 180 if (idSet == null) { 181 return NO_INSTANCE_ID; 182 } 183 for (int i = 0; i < MAX_GET_ID_RETRY_COUNT; i++) { 184 // Range [1, MAX_INSTANCE_ID] 185 int id = (int)Math.floor(Math.random() * MAX_INSTANCE_ID) + 1; 186 if (!idSet.contains(id)) { 187 return id; 188 } 189 } 190 return NO_INSTANCE_ID; 191 } 192 193 /** 194 * Gets all existing instance IDs. 195 * 196 * @return a set of all existing instance IDs 197 */ getExistingIds()198 private Set<Integer> getExistingIds() { 199 if (mDirectory == null || !mDirectory.isDirectory()) { 200 return null; 201 } 202 203 Set<Integer> idSet = new HashSet<>(); 204 for (File file : mDirectory.listFiles()) { 205 int id = getFileId(file); 206 if(id != NO_INSTANCE_ID) { 207 idSet.add(id); 208 } 209 } 210 return idSet; 211 } 212 213 /** 214 * Gets the instance ID of a given file. 215 * 216 * @param file the file whose TsStreamWriter ID is returned 217 * @return the TsStreamWriter ID of the file or NO_INSTANCE_ID if not available 218 */ getFileId(File file)219 private static int getFileId(File file) { 220 if (file == null || !file.isFile()) { 221 return NO_INSTANCE_ID; 222 } 223 String fileName = file.getName(); 224 int lastSeparator = fileName.lastIndexOf(SEPARATOR); 225 if (!fileName.endsWith(".ts") || lastSeparator == -1) { 226 return NO_INSTANCE_ID; 227 } 228 try { 229 return Integer.parseInt(fileName.substring(lastSeparator + 1, fileName.length() - 3)); 230 } catch (NumberFormatException e) { 231 if (DEBUG) { 232 Log.e(TAG, fileName + " is not a valid file name."); 233 } 234 } 235 return NO_INSTANCE_ID; 236 } 237 } 238