1 /* 2 * Copyright (C) 2015 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 package com.android.tv.tuner.exoplayer.tests; 17 18 import static com.google.common.truth.Truth.assertWithMessage; 19 20 import static junit.framework.Assert.fail; 21 22 import android.content.Context; 23 import android.net.Uri; 24 import android.os.HandlerThread; 25 import android.os.Looper; 26 import android.util.Pair; 27 28 import com.android.tv.testing.constants.ConfigConstants; 29 import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor; 30 import com.android.tv.tuner.exoplayer.buffer.BufferManager; 31 import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager; 32 import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener; 33 import com.android.tv.tuner.exoplayer.buffer.SampleChunk; 34 import com.android.tv.tuner.testing.buffer.VerySlowSampleChunk; 35 36 import com.google.android.exoplayer.MediaFormat; 37 import com.google.android.exoplayer.SampleHolder; 38 import com.google.android.exoplayer.SampleSource; 39 import com.google.android.exoplayer2.upstream.DataSource; 40 41 import org.junit.Before; 42 import org.junit.Ignore; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.robolectric.RobolectricTestRunner; 46 import org.robolectric.RuntimeEnvironment; 47 import org.robolectric.Shadows; 48 import org.robolectric.annotation.Config; 49 import org.robolectric.shadows.ShadowLooper; 50 51 import java.io.File; 52 import java.io.IOException; 53 import java.util.ArrayList; 54 import java.util.List; 55 import java.util.SortedMap; 56 57 /** Tests for {@link ExoPlayerSampleExtractor} */ 58 @RunWith(RobolectricTestRunner.class) 59 @Config(sdk = ConfigConstants.SDK) 60 public class SampleSourceExtractorTest { 61 // Maximum bandwidth of 1080p channel is about 2.2MB/s. 2MB for a sample will suffice. 62 private static final int SAMPLE_BUFFER_SIZE = 1024 * 1024 * 2; 63 private static final int CONSUMING_SAMPLES_PERIOD = 100; 64 private Uri testStreamUri; 65 private HandlerThread handlerThread; 66 private DataSource dataSource; 67 68 @Before setUp()69 public void setUp() { 70 testStreamUri = Uri.parse("asset:///capture_stream.ts"); 71 handlerThread = new HandlerThread("test"); 72 dataSource = new AssetDataSource(RuntimeEnvironment.application); 73 } 74 75 @Test testTrickplayDisabled()76 public void testTrickplayDisabled() throws Throwable { 77 DataSource source = new AssetDataSource(RuntimeEnvironment.application); 78 MockPlaybackBufferListener listener = new MockPlaybackBufferListener(); 79 ExoPlayerSampleExtractor extractor = 80 new ExoPlayerSampleExtractor( 81 testStreamUri, 82 source, 83 null, 84 listener, 85 false, 86 Looper.getMainLooper(), 87 handlerThread, 88 (bufferManager, bufferListener, enableTrickplay, bufferReason) -> null); 89 assertWithMessage("Trickplay should be disabled").that(listener.getLastState()).isFalse(); 90 // Prepares the extractor. 91 extractor.prepare(); 92 // Looper is nat available until prepare is called at least once 93 Looper handlerLooper = handlerThread.getLooper(); 94 try { 95 while (!extractor.prepare()) { 96 97 ShadowLooper.getShadowMainLooper().runOneTask(); 98 Shadows.shadowOf(handlerLooper).runOneTask(); 99 } 100 } catch (IOException e) { 101 fail("Exception occurred while preparing: " + e.getMessage()); 102 } 103 // Selects all tracks. 104 List<MediaFormat> trackFormats = extractor.getTrackFormats(); 105 for (int i = 0; i < trackFormats.size(); ++i) { 106 extractor.selectTrack(i); 107 } 108 // Consumes over some period. 109 SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); 110 sampleHolder.ensureSpaceForWrite(SAMPLE_BUFFER_SIZE); 111 112 Shadows.shadowOf(handlerLooper).idle(); 113 for (int i = 0; i < CONSUMING_SAMPLES_PERIOD; ++i) { 114 boolean found = false; 115 while (!found) { 116 for (int j = 0; j < trackFormats.size(); ++j) { 117 int result = extractor.readSample(j, sampleHolder); 118 switch (result) { 119 case SampleSource.SAMPLE_READ: 120 found = true; 121 break; 122 case SampleSource.END_OF_STREAM: 123 fail("Failed to read samples"); 124 break; 125 default: 126 } 127 if (found) { 128 break; 129 } 130 } 131 Shadows.shadowOf(handlerLooper).runOneTask(); 132 ShadowLooper.getShadowMainLooper().runOneTask(); 133 } 134 } 135 extractor.release(); 136 } 137 138 @Ignore("b/70338667") 139 @Test testDiskTooSlowTrickplayDisabled()140 public void testDiskTooSlowTrickplayDisabled() throws Throwable { 141 StorageManager storageManager = new StubStorageManager(RuntimeEnvironment.application); 142 BufferManager bufferManager = 143 new BufferManager( 144 storageManager, new VerySlowSampleChunk.VerySlowSampleChunkCreator()); 145 bufferManager.setMinimumSampleSizeForSpeedCheck(0); 146 MockPlaybackBufferListener listener = new MockPlaybackBufferListener(); 147 ExoPlayerSampleExtractor extractor = 148 new ExoPlayerSampleExtractor( 149 testStreamUri, 150 dataSource, 151 bufferManager, 152 listener, 153 false, 154 Looper.getMainLooper(), 155 handlerThread, 156 (bufferManager2, bufferListener, enableTrickplay, bufferReason) -> null); 157 158 assertWithMessage("Trickplay should be enabled at the first") 159 .that(Boolean.TRUE) 160 .isEqualTo(listener.getLastState()); 161 // Prepares the extractor. 162 extractor.prepare(); 163 // Looper is nat available until prepare is called at least once 164 Looper handlerLooper = handlerThread.getLooper(); 165 try { 166 while (!extractor.prepare()) { 167 168 ShadowLooper.getShadowMainLooper().runOneTask(); 169 Shadows.shadowOf(handlerLooper).runOneTask(); 170 } 171 } catch (IOException e) { 172 fail("Exception occurred while preparing: " + e.getMessage()); 173 } 174 // Selects all tracks. 175 List<MediaFormat> trackFormats = extractor.getTrackFormats(); 176 for (int i = 0; i < trackFormats.size(); ++i) { 177 extractor.selectTrack(i); 178 } 179 // Consumes until once speed check is done. 180 SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); 181 sampleHolder.ensureSpaceForWrite(SAMPLE_BUFFER_SIZE); 182 while (!bufferManager.hasSpeedCheckDone()) { 183 boolean found = false; 184 while (!found) { 185 for (int j = 0; j < trackFormats.size(); ++j) { 186 int result = extractor.readSample(j, sampleHolder); 187 switch (result) { 188 case SampleSource.SAMPLE_READ: 189 found = true; 190 break; 191 case SampleSource.END_OF_STREAM: 192 fail("Failed to read samples"); 193 break; 194 default: 195 } 196 if (found) { 197 break; 198 } 199 } 200 ShadowLooper.getShadowMainLooper().runOneTask(); 201 Shadows.shadowOf(handlerLooper).runOneTask(); 202 } 203 } 204 extractor.release(); 205 ShadowLooper.getShadowMainLooper().idle(); 206 Shadows.shadowOf(handlerLooper).idle(); 207 assertWithMessage("Disk too slow event should be reported") 208 .that(listener.isReportedDiskTooSlow()) 209 .isTrue(); 210 } 211 212 private static class StubStorageManager implements StorageManager { 213 private final Context mContext; 214 StubStorageManager(Context context)215 StubStorageManager(Context context) { 216 mContext = context; 217 } 218 219 @Override getBufferDir()220 public File getBufferDir() { 221 return mContext.getCacheDir(); 222 } 223 224 @Override isPersistent()225 public boolean isPersistent() { 226 return false; 227 } 228 229 @Override reachedStorageMax(long bufferSize, long pendingDelete)230 public boolean reachedStorageMax(long bufferSize, long pendingDelete) { 231 return false; 232 } 233 234 @Override hasEnoughBuffer(long pendingDelete)235 public boolean hasEnoughBuffer(long pendingDelete) { 236 return true; 237 } 238 239 @Override readTrackInfoFiles(boolean isAudio)240 public List<BufferManager.TrackFormat> readTrackInfoFiles(boolean isAudio) { 241 return null; 242 } 243 244 @Override readIndexFile(String trackId)245 public ArrayList<BufferManager.PositionHolder> readIndexFile(String trackId) 246 throws IOException { 247 return null; 248 } 249 250 @Override writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio)251 public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio) 252 throws IOException { 253 // No-op. 254 } 255 256 @Override writeIndexFile( String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)257 public void writeIndexFile( 258 String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index) 259 throws IOException { 260 // No-op. 261 } 262 263 @Override updateIndexFile( String trackName, int size, long position, SampleChunk sampleChunk, int offset)264 public void updateIndexFile( 265 String trackName, int size, long position, SampleChunk sampleChunk, int offset) 266 throws IOException { 267 // No-op 268 } 269 } 270 271 public static class MockPlaybackBufferListener implements PlaybackBufferListener { 272 private Boolean mLastState; 273 private boolean mIsReportedDiskTooSlow; 274 getLastState()275 public Boolean getLastState() { 276 return mLastState; 277 } 278 isReportedDiskTooSlow()279 public boolean isReportedDiskTooSlow() { 280 return mIsReportedDiskTooSlow; 281 } 282 // PlaybackBufferListener 283 @Override onBufferStartTimeChanged(long startTimeMs)284 public void onBufferStartTimeChanged(long startTimeMs) { 285 // No-op. 286 } 287 288 @Override onBufferStateChanged(boolean available)289 public void onBufferStateChanged(boolean available) { 290 mLastState = available; 291 } 292 293 @Override onDiskTooSlow()294 public void onDiskTooSlow() { 295 mIsReportedDiskTooSlow = true; 296 } 297 } 298 } 299