aboutsummaryrefslogtreecommitdiff
path: root/app/src/test/java/invalid/lena/scrcpy/AudioStreamTest.java
blob: 005a2e37b530e388d2dd0f7cf1a3068059e79d2b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package invalid.lena.scrcpy;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class AudioStreamTest {

    private static final long FLAG_CONFIG = 1L << 62;

    private static final class RecordingFrames implements AudioFrames {
        int starts = 0, releases = 0, startFourcc = 0;
        final List<byte[]>  feeds = new ArrayList<>();
        final List<Boolean> cfgs  = new ArrayList<>();
        @Override public void start(int fourcc) { starts++; startFourcc = fourcc; }
        @Override public void feed(byte[] data, int off, int len, boolean isConfig) {
            byte[] cp = new byte[len];
            System.arraycopy(data, off, cp, 0, len);
            feeds.add(cp);
            cfgs.add(isConfig);
        }
        @Override public void release() { releases++; }
    }

    private static byte[] fourcc(int v) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        new DataOutputStream(bos).writeInt(v);
        return bos.toByteArray();
    }

    private static byte[] frame(long ptsAndFlags, byte[] payload) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(bos);
        out.writeLong(ptsAndFlags);
        out.writeInt(payload.length);
        out.write(payload);
        return bos.toByteArray();
    }

    private static byte[] cat(byte[]... parts) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        for (byte[] p : parts) bos.write(p);
        return bos.toByteArray();
    }

    @Test
    public void rawCodecFeedsFrames() throws Exception {
        byte[] pcm1 = new byte[]{1, 2, 3, 4};
        byte[] pcm2 = new byte[]{5, 6, 7, 8, 9, 10};
        byte[] bytes = cat(
                fourcc(Wire.CODEC_RAW),
                frame(1_000_000L, pcm1),
                frame(2_000_000L, pcm2));

        RecordingFrames sink = new RecordingFrames();
        new AudioStream(new ByteArrayInputStream(bytes), sink).run();

        assertEquals(1, sink.starts);
        assertEquals(Wire.CODEC_RAW, sink.startFourcc);
        assertEquals(2, sink.feeds.size());
        assertEquals(pcm1.length, sink.feeds.get(0).length);
        assertEquals(pcm2.length, sink.feeds.get(1).length);
        for (int i = 0; i < pcm1.length; i++) assertEquals(pcm1[i], sink.feeds.get(0)[i]);
        for (int i = 0; i < pcm2.length; i++) assertEquals(pcm2[i], sink.feeds.get(1)[i]);
        assertEquals(false, sink.cfgs.get(0));
        assertEquals(false, sink.cfgs.get(1));
    }

    @Test
    public void opusConfigFlagIsExposed() throws Exception {
        byte[] head = new byte[]{
                'O','p','u','s','H','e','a','d', // magic
                1,                                // version
                2,                                // channel count
                (byte)0x38, 0x01,                 // pre-skip = 312 LE
                (byte)0x80, (byte)0xBB, 0, 0,     // sample rate 48000 LE
                0, 0,                             // output gain
                0                                 // channel mapping family
        };
        byte[] pkt = new byte[]{0x10, 0x20, 0x30};
        byte[] bytes = cat(
                fourcc(Wire.CODEC_OPUS),
                frame(FLAG_CONFIG, head),
                frame(0L, pkt));

        RecordingFrames sink = new RecordingFrames();
        new AudioStream(new ByteArrayInputStream(bytes), sink).run();

        assertEquals(1, sink.starts);
        assertEquals(Wire.CODEC_OPUS, sink.startFourcc);
        assertEquals(2, sink.feeds.size());
        assertTrue("first frame must carry FLAG_CONFIG", sink.cfgs.get(0));
        assertTrue("second frame must not carry FLAG_CONFIG", !sink.cfgs.get(1));
        assertEquals(head.length, sink.feeds.get(0).length);
        assertEquals(pkt.length, sink.feeds.get(1).length);
    }

    @Test
    public void disabledCodecDoesNotStart() throws Exception {
        byte[] bytes = fourcc(0);
        RecordingFrames sink = new RecordingFrames();
        new AudioStream(new ByteArrayInputStream(bytes), sink).run();
        assertEquals(0, sink.starts);
        assertEquals(0, sink.feeds.size());
    }

    @Test
    public void errorCodecDoesNotStart() throws Exception {
        byte[] bytes = fourcc(1);
        RecordingFrames sink = new RecordingFrames();
        AtomicInteger fatal = new AtomicInteger();
        new AudioStream(new ByteArrayInputStream(bytes), sink, fatal::incrementAndGet).run();
        assertEquals(0, sink.starts);
        assertEquals(0, sink.feeds.size());
        assertEquals(1, fatal.get());
    }

    @Test
    public void disabledCodecIsNotFatal() throws Exception {
        AtomicInteger fatal = new AtomicInteger();
        new AudioStream(new ByteArrayInputStream(fourcc(0)),
                new RecordingFrames(), fatal::incrementAndGet).run();
        assertEquals(0, fatal.get());
    }
}