Home | History | Annotate | Download | only in tuner
      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;
     18 
     19 import android.content.Context;
     20 import android.graphics.SurfaceTexture;
     21 import android.os.Handler;
     22 import android.os.HandlerThread;
     23 import android.os.Message;
     24 import android.support.test.filters.LargeTest;
     25 import android.test.InstrumentationTestCase;
     26 import android.util.Log;
     27 import android.view.Surface;
     28 import com.android.tv.tuner.data.Cea708Data;
     29 import com.android.tv.tuner.data.PsiData;
     30 import com.android.tv.tuner.data.PsipData;
     31 import com.android.tv.tuner.data.TunerChannel;
     32 import com.android.tv.tuner.data.nano.Channel;
     33 import com.android.tv.tuner.exoplayer.MpegTsPlayer;
     34 import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
     35 import com.android.tv.tuner.exoplayer.buffer.BufferManager;
     36 import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager;
     37 import com.android.tv.tuner.source.TsDataSourceManager;
     38 import com.android.tv.tuner.tvinput.EventDetector;
     39 import com.android.tv.tuner.tvinput.PlaybackBufferListener;
     40 import com.google.android.exoplayer.ExoPlayer;
     41 import java.io.File;
     42 import java.io.FileOutputStream;
     43 import java.io.IOException;
     44 import java.io.InputStream;
     45 import java.util.ArrayList;
     46 import java.util.Arrays;
     47 import java.util.List;
     48 import java.util.concurrent.CountDownLatch;
     49 import java.util.concurrent.TimeUnit;
     50 import java.util.concurrent.atomic.AtomicLong;
     51 import org.junit.Ignore;
     52 
     53 /** This class use {@link FileTunerHal} to simulate tunerhal's actions to test zapping time. */
     54 @LargeTest
     55 public class ZappingTimeTest extends InstrumentationTestCase {
     56     private static final String TAG = "ZappingTimeTest";
     57     private static final boolean DEBUG = false;
     58     private static final int TS_COPY_BUFFER_SIZE = 1024 * 512;
     59     private static final int PROGRAM_NUMBER = 1;
     60     private static final int VIDEO_PID = 49;
     61     private static final int PCR_PID = 49;
     62     private static final List<Integer> AUDIO_PIDS = Arrays.asList(51, 52, 53);
     63     private static final long BUFFER_SIZE_DEF = 2 * 1024;
     64     private static final int FREQUENCY = -1;
     65     private static final String MODULATION = "";
     66     private static final long ZAPPING_TIME_OUT_MS = 10000;
     67     private static final long MAX_AVERAGE_ZAPPING_TIME_MS = 4000;
     68     private static final int TEST_ITERATION_COUNT = 10;
     69     private static final int STRESS_ZAPPING_TEST_COUNT = 50;
     70     private static final long SKIP_DURATION_MS_TO_ADD = 200;
     71     private static final String TEST_TS_FILE_PATH = "capture_kqed.ts";
     72 
     73     private static final int MSG_START_PLAYBACK = 1;
     74 
     75     private TunerChannel mChannel;
     76     private FileTunerHal mTunerHal;
     77     private MpegTsPlayer mPlayer;
     78     private TsDataSourceManager mSourceManager;
     79     private Handler mHandler;
     80     private Context mTargetContext;
     81     private File mTrickplayBufferDir;
     82     private Surface mSurface;
     83     private CountDownLatch mErrorLatch;
     84     private CountDownLatch mDrawnToSurfaceLatch;
     85     private CountDownLatch mWaitTuneExecuteLatch;
     86     private AtomicLong mOnDrawnToSurfaceTimeMs = new AtomicLong(0);
     87     private MockMpegTsPlayerListener mMpegTsPlayerListener = new MockMpegTsPlayerListener();
     88     private MockPlaybackBufferListener mPlaybackBufferListener = new MockPlaybackBufferListener();
     89     private MockEventListener mEventListener = new MockEventListener();
     90 
     91     @Override
     92     protected void setUp() throws Exception {
     93         super.setUp();
     94         mTargetContext = getInstrumentation().getTargetContext();
     95         mTrickplayBufferDir = mTargetContext.getCacheDir();
     96         HandlerThread handlerThread = new HandlerThread(TAG);
     97         handlerThread.start();
     98         List<PsiData.PmtItem> pmtItems = new ArrayList<>();
     99         pmtItems.add(new PsiData.PmtItem(Channel.VideoStreamType.MPEG2, VIDEO_PID, null, null));
    100         for (int audioPid : AUDIO_PIDS) {
    101             pmtItems.add(
    102                     new PsiData.PmtItem(Channel.AudioStreamType.A52AC3AUDIO, audioPid, null, null));
    103         }
    104 
    105         Context context = getInstrumentation().getContext();
    106         // Since assets and resource files are compressed, random access to the specified offset
    107         // in assets or resource files will add some delay which is proportional to the offset.
    108         // So the TS stream asset file are copied to a cache file, and the starting stream position
    109         // in the file will be accessed by underlying {@link RandomAccessFile}.
    110         File tsCacheFile = createCacheFile(context, mTargetContext, TEST_TS_FILE_PATH);
    111         pmtItems.add(new PsiData.PmtItem(0x100, PCR_PID, null, null));
    112         mChannel = new TunerChannel(PROGRAM_NUMBER, pmtItems);
    113         mChannel.setFrequency(FREQUENCY);
    114         mChannel.setModulation(MODULATION);
    115         mTunerHal = new FileTunerHal(context, tsCacheFile);
    116         mTunerHal.openFirstAvailable();
    117         mSourceManager = TsDataSourceManager.createSourceManager(false);
    118         mSourceManager.addTunerHalForTest(mTunerHal);
    119         mHandler =
    120                 new Handler(
    121                         handlerThread.getLooper(),
    122                         new Handler.Callback() {
    123                             @Override
    124                             public boolean handleMessage(Message msg) {
    125                                 switch (msg.what) {
    126                                     case MSG_START_PLAYBACK:
    127                                         {
    128                                             mHandler.removeCallbacksAndMessages(null);
    129                                             stopPlayback();
    130                                             mOnDrawnToSurfaceTimeMs.set(0);
    131                                             mDrawnToSurfaceLatch = new CountDownLatch(1);
    132                                             if (mWaitTuneExecuteLatch != null) {
    133                                                 mWaitTuneExecuteLatch.countDown();
    134                                             }
    135                                             int frequency = msg.arg1;
    136                                             boolean useSimpleSampleBuffer = (msg.arg2 == 1);
    137                                             BufferManager bufferManager = null;
    138                                             if (!useSimpleSampleBuffer) {
    139                                                 bufferManager =
    140                                                         new BufferManager(
    141                                                                 new TrickplayStorageManager(
    142                                                                         mTargetContext,
    143                                                                         mTrickplayBufferDir,
    144                                                                         1024L
    145                                                                                 * 1024L
    146                                                                                 * BUFFER_SIZE_DEF));
    147                                             }
    148                                             mChannel.setFrequency(frequency);
    149                                             mSourceManager.setKeepTuneStatus(true);
    150                                             mPlayer =
    151                                                     new MpegTsPlayer(
    152                                                             new MpegTsRendererBuilder(
    153                                                                     mTargetContext,
    154                                                                     bufferManager,
    155                                                                     mPlaybackBufferListener),
    156                                                             mHandler,
    157                                                             mSourceManager,
    158                                                             null,
    159                                                             mMpegTsPlayerListener);
    160                                             mPlayer.setCaptionServiceNumber(
    161                                                     Cea708Data.EMPTY_SERVICE_NUMBER);
    162                                             mPlayer.prepare(
    163                                                     mTargetContext,
    164                                                     mChannel,
    165                                                     false,
    166                                                     mEventListener);
    167                                             return true;
    168                                         }
    169                                     default:
    170                                         {
    171                                             Log.i(TAG, "Unhandled message code: " + msg.what);
    172                                             return true;
    173                                         }
    174                                 }
    175                             }
    176                         });
    177     }
    178 
    179     @Override
    180     protected void tearDown() throws Exception {
    181         if (mPlayer != null) {
    182             mPlayer.release();
    183         }
    184         if (mSurface != null) {
    185             mSurface.release();
    186         }
    187         mHandler.getLooper().quitSafely();
    188         super.tearDown();
    189     }
    190 
    191     public void testZappingTime() {
    192         zappingTimeTest(false, TEST_ITERATION_COUNT, true);
    193     }
    194 
    195     public void testZappingTimeWithSimpleSampleBuffer() {
    196         zappingTimeTest(true, TEST_ITERATION_COUNT, true);
    197     }
    198 
    199     @Ignore("b/69978026")
    200     @SuppressWarnings("JUnit4ClassUsedInJUnit3")
    201     public void testStressZapping() {
    202         zappingTimeTest(false, STRESS_ZAPPING_TEST_COUNT, false);
    203     }
    204 
    205     @Ignore("b/69978093")
    206     @SuppressWarnings("JUnit4ClassUsedInJUnit3")
    207     public void testZappingWithPacketMissing() {
    208         mTunerHal.setEnablePacketMissing(true);
    209         mTunerHal.setEnableArtificialDelay(true);
    210         SurfaceTexture surfaceTexture = new SurfaceTexture(0);
    211         mSurface = new Surface(surfaceTexture);
    212         long zappingStartTimeMs = System.currentTimeMillis();
    213         mErrorLatch = new CountDownLatch(1);
    214         mHandler.obtainMessage(MSG_START_PLAYBACK, FREQUENCY, 0).sendToTarget();
    215         boolean errorAppeared = false;
    216         while (System.currentTimeMillis() - zappingStartTimeMs < ZAPPING_TIME_OUT_MS) {
    217             try {
    218                 errorAppeared = mErrorLatch.await(100, TimeUnit.MILLISECONDS);
    219                 if (errorAppeared) {
    220                     break;
    221                 }
    222             } catch (InterruptedException e) {
    223             }
    224         }
    225         assertFalse("Error happened when packet lost", errorAppeared);
    226     }
    227 
    228     private static File createCacheFile(Context context, Context targetContext, String filename)
    229             throws IOException {
    230         File cacheFile = new File(targetContext.getCacheDir(), filename);
    231 
    232         if (cacheFile.createNewFile() == false) {
    233             cacheFile.delete();
    234             cacheFile.createNewFile();
    235         }
    236 
    237         InputStream inputStream = context.getResources().getAssets().open(filename);
    238         FileOutputStream fileOutputStream = new FileOutputStream(cacheFile);
    239 
    240         byte[] buffer = new byte[TS_COPY_BUFFER_SIZE];
    241         while (inputStream.read(buffer, 0, TS_COPY_BUFFER_SIZE) != -1) {
    242             fileOutputStream.write(buffer);
    243         }
    244 
    245         fileOutputStream.close();
    246         inputStream.close();
    247 
    248         return cacheFile;
    249     }
    250 
    251     private void zappingTimeTest(
    252             boolean useSimpleSampleBuffer, int testIterationCount, boolean enableArtificialDelay) {
    253         String bufferManagerLogString =
    254                 !enableArtificialDelay
    255                         ? "for stress test"
    256                         : useSimpleSampleBuffer ? "with simple sample buffer" : "";
    257         SurfaceTexture surfaceTexture = new SurfaceTexture(0);
    258         mSurface = new Surface(surfaceTexture);
    259         mTunerHal.setEnablePacketMissing(false);
    260         mTunerHal.setEnableArtificialDelay(enableArtificialDelay);
    261         double totalZappingTime = 0.0;
    262         for (int i = 0; i < testIterationCount; i++) {
    263             mWaitTuneExecuteLatch = new CountDownLatch(1);
    264             long zappingStartTimeMs = System.currentTimeMillis();
    265             mTunerHal.setInitialSkipMs(SKIP_DURATION_MS_TO_ADD * (i % TEST_ITERATION_COUNT));
    266             mHandler.obtainMessage(MSG_START_PLAYBACK, FREQUENCY + i, useSimpleSampleBuffer ? 1 : 0)
    267                     .sendToTarget();
    268             try {
    269                 mWaitTuneExecuteLatch.await();
    270             } catch (InterruptedException e) {
    271             }
    272             boolean drawnToSurface = false;
    273             while (System.currentTimeMillis() - zappingStartTimeMs < ZAPPING_TIME_OUT_MS) {
    274                 try {
    275                     drawnToSurface = mDrawnToSurfaceLatch.await(100, TimeUnit.MILLISECONDS);
    276                     if (drawnToSurface) {
    277                         break;
    278                     }
    279                 } catch (InterruptedException e) {
    280                 }
    281             }
    282             if (i == 0) {
    283                 continue;
    284                 // Get rid of the first result, which shows outlier often.
    285             }
    286             // In 10s, all zapping request will finish. Set the maximum zapping time as 10s could be
    287             // reasonable.
    288             totalZappingTime +=
    289                     (mOnDrawnToSurfaceTimeMs.get() > 0
    290                             ? mOnDrawnToSurfaceTimeMs.get() - zappingStartTimeMs
    291                             : ZAPPING_TIME_OUT_MS);
    292         }
    293         double averageZappingTime = totalZappingTime / (testIterationCount - 1);
    294         Log.i(TAG, "Average zapping time " + bufferManagerLogString + ":" + averageZappingTime);
    295         assertTrue(
    296                 "Average Zapping time "
    297                         + bufferManagerLogString
    298                         + " is too large:"
    299                         + averageZappingTime,
    300                 averageZappingTime < MAX_AVERAGE_ZAPPING_TIME_MS);
    301     }
    302 
    303     private void stopPlayback() {
    304         if (mPlayer != null) {
    305             mPlayer.setPlayWhenReady(false);
    306             mPlayer.release();
    307             mPlayer = null;
    308         }
    309     }
    310 
    311     private class MockMpegTsPlayerListener implements MpegTsPlayer.Listener {
    312 
    313         @Override
    314         public void onStateChanged(boolean playWhenReady, int playbackState) {
    315             if (DEBUG) {
    316                 Log.d(TAG, "ExoPlayer state change: " + playbackState + " " + playWhenReady);
    317             }
    318             if (playbackState == ExoPlayer.STATE_READY) {
    319                 mPlayer.setSurface(mSurface);
    320                 mPlayer.setPlayWhenReady(true);
    321                 mPlayer.setVolume(1.0f);
    322             }
    323         }
    324 
    325         @Override
    326         public void onError(Exception e) {
    327             if (DEBUG) {
    328                 Log.d(TAG, "onError");
    329             }
    330             if (mErrorLatch != null) {
    331                 mErrorLatch.countDown();
    332             }
    333         }
    334 
    335         @Override
    336         public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) {
    337             if (DEBUG) {
    338                 Log.d(TAG, "onVideoSizeChanged");
    339             }
    340         }
    341 
    342         @Override
    343         public void onDrawnToSurface(MpegTsPlayer player, Surface surface) {
    344             if (DEBUG) {
    345                 Log.d(TAG, "onDrawnToSurface");
    346             }
    347             mOnDrawnToSurfaceTimeMs.set(System.currentTimeMillis());
    348             if (mDrawnToSurfaceLatch != null) {
    349                 mDrawnToSurfaceLatch.countDown();
    350             }
    351         }
    352 
    353         @Override
    354         public void onAudioUnplayable() {
    355             if (DEBUG) {
    356                 Log.d(TAG, "onAudioUnplayable");
    357             }
    358         }
    359 
    360         @Override
    361         public void onSmoothTrickplayForceStopped() {
    362             if (DEBUG) {
    363                 Log.d(TAG, "onSmoothTrickplayForceStopped");
    364             }
    365         }
    366     }
    367 
    368     private static class MockPlaybackBufferListener implements PlaybackBufferListener {
    369         @Override
    370         public void onBufferStartTimeChanged(long startTimeMs) {
    371             if (DEBUG) {
    372                 Log.d(TAG, "onBufferStartTimeChanged");
    373             }
    374         }
    375 
    376         @Override
    377         public void onBufferStateChanged(boolean available) {
    378             if (DEBUG) {
    379                 Log.d(TAG, "onBufferStateChanged");
    380             }
    381         }
    382 
    383         @Override
    384         public void onDiskTooSlow() {
    385             if (DEBUG) {
    386                 Log.d(TAG, "onDiskTooSlow");
    387             }
    388         }
    389     }
    390 
    391     private static class MockEventListener implements EventDetector.EventListener {
    392         @Override
    393         public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
    394             if (DEBUG) {
    395                 Log.d(TAG, "onChannelDetected");
    396             }
    397         }
    398 
    399         @Override
    400         public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) {
    401             if (DEBUG) {
    402                 Log.d(TAG, "onEventDetected");
    403             }
    404         }
    405 
    406         @Override
    407         public void onChannelScanDone() {
    408             if (DEBUG) {
    409                 Log.d(TAG, "onChannelScanDone");
    410             }
    411         }
    412     }
    413 }
    414