Home | History | Annotate | Download | only in source
      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 
     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      */
     75     public void setChannel(TunerChannel channel) {
     76         mChannel = channel;
     77     }
     78 
     79     /** Opens a file to store TS data. */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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