1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.media;
6 
7 import static org.junit.Assert.assertArrayEquals;
8 import static org.junit.Assert.assertEquals;
9 import static org.junit.Assert.assertTrue;
10 
11 import android.media.AudioFormat;
12 import android.media.AudioTrack;
13 
14 import org.junit.Test;
15 import org.junit.runner.RunWith;
16 import org.robolectric.annotation.Config;
17 
18 import org.chromium.media.AudioTrackOutputStream.AudioBufferInfo;
19 import org.chromium.testing.local.LocalRobolectricTestRunner;
20 
21 import java.nio.ByteBuffer;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.TimeUnit;
26 
27 /**
28  * Tests for AudioTrackOutputStream.
29  */
30 @RunWith(LocalRobolectricTestRunner.class)
31 @Config(manifest = Config.NONE)
32 public class AudioTrackOutputStreamTest {
33     static class ObservableAudioTrack extends AudioTrack {
34         private List<Byte> mReceivedData = new ArrayList<Byte>();
35         private boolean mPartialWrite = true;
36 
ObservableAudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)37         public ObservableAudioTrack(int streamType, int sampleRateInHz, int channelConfig,
38                 int audioFormat, int bufferSizeInBytes, int mode) {
39             super(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);
40         }
41 
42         @Override
write(ByteBuffer audioData, int sizeInBytes, int writeMode)43         public int write(ByteBuffer audioData, int sizeInBytes, int writeMode) {
44             int writternSize = mPartialWrite ? sizeInBytes / 2 : sizeInBytes;
45             mPartialWrite = !mPartialWrite;
46 
47             if (writternSize > 0) {
48                 byte[] array = new byte[writternSize];
49                 audioData.get(array);
50                 recordData(array, 0, writternSize);
51             }
52 
53             return writternSize;
54         }
55 
56         @Override
write(byte[] audioData, int offsetInBytes, int sizeInBytes)57         public int write(byte[] audioData, int offsetInBytes, int sizeInBytes) {
58             int writternSize = mPartialWrite ? sizeInBytes / 2 : sizeInBytes;
59             mPartialWrite = !mPartialWrite;
60 
61             if (writternSize > 0) recordData(audioData, offsetInBytes, writternSize);
62 
63             return writternSize;
64         }
65 
recordData(byte[] audioData, int offsetInBytes, int sizeInBytes)66         private void recordData(byte[] audioData, int offsetInBytes, int sizeInBytes) {
67             for (; sizeInBytes > 0; --sizeInBytes) mReceivedData.add(audioData[offsetInBytes++]);
68         }
69 
getReceivedData()70         public List<Byte> getReceivedData() {
71             return mReceivedData;
72         }
73     }
74 
75     static class DataProvider implements AudioTrackOutputStream.Callback {
76         private static final int MIN_BUFFER_SIZE = 800;
77         private List<Byte> mGeneratedData = new ArrayList<Byte>();
78         private CountDownLatch mDoneSignal;
79         private ObservableAudioTrack mAudioTrack;
80 
DataProvider(int bufferCount)81         public DataProvider(int bufferCount) {
82             assert bufferCount > 0;
83             mDoneSignal = new CountDownLatch(bufferCount + 1);
84         }
85 
updateBufferCount(int bufferCount)86         public void updateBufferCount(int bufferCount) {
87             assert bufferCount > 0;
88             mDoneSignal = new CountDownLatch(bufferCount + 1);
89         }
90 
91         @Override
getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)92         public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
93             return MIN_BUFFER_SIZE;
94         }
95 
96         @Override
createAudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)97         public AudioTrack createAudioTrack(int streamType, int sampleRateInHz, int channelConfig,
98                 int audioFormat, int bufferSizeInBytes, int mode) {
99             mAudioTrack = new ObservableAudioTrack(streamType, sampleRateInHz, channelConfig,
100                     audioFormat, bufferSizeInBytes, mode);
101             return mAudioTrack;
102         }
103 
104         @Override
onMoreData(ByteBuffer audioData, long delayInFrames)105         public AudioBufferInfo onMoreData(ByteBuffer audioData, long delayInFrames) {
106             mDoneSignal.countDown();
107             if (mDoneSignal.getCount() <= 0) {
108                 try {
109                     Thread.sleep(3);
110                 } catch (Exception e) {
111                 }
112                 return null;
113             }
114 
115             final int dataSize = MIN_BUFFER_SIZE;
116             for (int i = 0; i < dataSize; ++i) {
117                 byte data = (byte) i;
118                 audioData.put(data);
119                 mGeneratedData.add(data);
120             }
121             return new AudioBufferInfo(dataSize, dataSize);
122         }
123 
124         @Override
getAddress(ByteBuffer byteBuffer)125         public long getAddress(ByteBuffer byteBuffer) {
126             return 0x10001L;
127         }
128 
129         @Override
onError()130         public void onError() {}
131 
waitForOutOfData()132         public void waitForOutOfData() throws InterruptedException {
133             mDoneSignal.await(300, TimeUnit.MILLISECONDS);
134         }
135 
getGeneratedData()136         public List<Byte> getGeneratedData() {
137             return mGeneratedData;
138         }
139 
getReceivedData()140         public List<Byte> getReceivedData() {
141             return mAudioTrack.getReceivedData();
142         }
143 
getBufferSize()144         public int getBufferSize() {
145             return MIN_BUFFER_SIZE;
146         }
147     };
148 
149     @Test
playSimpleBitstream()150     public void playSimpleBitstream() throws InterruptedException {
151         DataProvider provider = new DataProvider(3);
152 
153         AudioTrackOutputStream stream = AudioTrackOutputStream.create(provider);
154         stream.open(2, 44100, AudioFormat.ENCODING_E_AC3);
155         stream.start(0x888);
156 
157         provider.waitForOutOfData();
158         List<Byte> generatedData = provider.getGeneratedData();
159         List<Byte> receivedData = provider.getReceivedData();
160 
161         assertEquals(3 * provider.getBufferSize(), generatedData.size());
162         assertArrayEquals(generatedData.toArray(), receivedData.toArray());
163 
164         stream.stop();
165         stream.close();
166     }
167 
168     @Test
playPiecewiseBitstream()169     public void playPiecewiseBitstream() throws InterruptedException {
170         DataProvider provider = new DataProvider(3);
171 
172         AudioTrackOutputStream stream = AudioTrackOutputStream.create(provider);
173         stream.open(2, 44100, AudioFormat.ENCODING_E_AC3);
174         stream.start(0x888);
175 
176         provider.waitForOutOfData();
177 
178         provider.updateBufferCount(3);
179         provider.waitForOutOfData();
180 
181         List<Byte> generatedData = provider.getGeneratedData();
182         List<Byte> receivedData = provider.getReceivedData();
183 
184         assertTrue(6 * provider.getBufferSize() <= generatedData.size());
185         assertArrayEquals(generatedData.toArray(), receivedData.toArray());
186 
187         stream.stop();
188         stream.close();
189     }
190 }
191