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 
     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 
     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      */
     78     public void setChannel(TunerChannel channel) {
     79         mChannel = channel;
     80     }
     81 
     82     /**
     83      * Opens a file to store TS data.
     84      */
     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      */
    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
    145      *                  {@code 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() && 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      */
    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      */
    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      */
    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