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