summaryrefslogtreecommitdiffstats
path: root/media/libcubeb/test/test_callback_ret.cpp
blob: 0737b60ae88303fc9577a3a561836bec7a3f0526 (plain)
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
 * Copyright � 2017 Mozilla Foundation
 *
 * This program is made available under an ISC-style license.  See the
 * accompanying file LICENSE for details.
 */

/* libcubeb api/function test. Test that different return values from user
   specified callbacks are handled correctly. */
#include "gtest/gtest.h"
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 600
#endif
#include "cubeb/cubeb.h"
#include <atomic>
#include <memory>
#include <string>

// #define ENABLE_NORMAL_LOG
// #define ENABLE_VERBOSE_LOG
#include "common.h"

const uint32_t SAMPLE_FREQUENCY = 48000;
const cubeb_sample_format SAMPLE_FORMAT = CUBEB_SAMPLE_S16NE;

enum test_direction { INPUT_ONLY, OUTPUT_ONLY, DUPLEX };

// Structure which is used by data callbacks to track the total callbacks
// executed vs the number of callbacks expected.
struct user_state_callback_ret {
  std::atomic<int> cb_count{0};
  std::atomic<int> expected_cb_count{0};
  std::atomic<int> error_state{0};
};

// Data callback that always returns 0
long
data_cb_ret_zero(cubeb_stream * stream, void * user, const void * inputbuffer,
                 void * outputbuffer, long nframes)
{
  user_state_callback_ret * u = (user_state_callback_ret *)user;
  // If this is the first time the callback has been called set our expected
  // callback count
  if (u->cb_count == 0) {
    u->expected_cb_count = 1;
  }
  u->cb_count++;
  if (nframes < 1) {
    // This shouldn't happen
    EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
  }
  return 0;
}

// Data callback that always returns nframes - 1
long
data_cb_ret_nframes_minus_one(cubeb_stream * stream, void * user,
                              const void * inputbuffer, void * outputbuffer,
                              long nframes)
{
  user_state_callback_ret * u = (user_state_callback_ret *)user;
  // If this is the first time the callback has been called set our expected
  // callback count
  if (u->cb_count == 0) {
    u->expected_cb_count = 1;
  }
  u->cb_count++;
  if (nframes < 1) {
    // This shouldn't happen
    EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
  }
  if (outputbuffer != NULL) {
    // If we have an output buffer insert silence
    short * ob = (short *)outputbuffer;
    for (long i = 0; i < nframes - 1; i++) {
      ob[i] = 0;
    }
  }
  return nframes - 1;
}

// Data callback that always returns nframes
long
data_cb_ret_nframes(cubeb_stream * stream, void * user,
                    const void * inputbuffer, void * outputbuffer, long nframes)
{
  user_state_callback_ret * u = (user_state_callback_ret *)user;
  u->cb_count++;
  // Every callback returns nframes, so every callback is expected
  u->expected_cb_count++;
  if (nframes < 1) {
    // This shouldn't happen
    EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
  }
  if (outputbuffer != NULL) {
    // If we have an output buffer insert silence
    short * ob = (short *)outputbuffer;
    for (long i = 0; i < nframes; i++) {
      ob[i] = 0;
    }
  }
  return nframes;
}

// Data callback that always returns CUBEB_ERROR
long
data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer,
                  void * outputbuffer, long nframes)
{
  user_state_callback_ret * u = (user_state_callback_ret *)user;
  // If this is the first time the callback has been called set our expected
  // callback count
  if (u->cb_count == 0) {
    u->expected_cb_count = 1;
  }
  u->cb_count++;
  if (nframes < 1) {
    // This shouldn't happen
    EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
  }
  return CUBEB_ERROR;
}

void
state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state)
{
  if (stream == NULL)
    return;
  user_state_callback_ret * u = (user_state_callback_ret *)user;

  switch (state) {
  case CUBEB_STATE_STARTED:
    fprintf(stderr, "stream started\n");
    break;
  case CUBEB_STATE_STOPPED:
    fprintf(stderr, "stream stopped\n");
    break;
  case CUBEB_STATE_DRAINED:
    fprintf(stderr, "stream drained\n");
    break;
  case CUBEB_STATE_ERROR:
    fprintf(stderr, "stream error\n");
    u->error_state.fetch_add(1);
    break;
  default:
    fprintf(stderr, "unknown stream state %d\n", state);
  }
}

void
run_test_callback(test_direction direction, cubeb_data_callback data_cb,
                  const std::string & test_desc)
{
  cubeb * ctx;
  cubeb_stream * stream;
  cubeb_stream_params input_params;
  cubeb_stream_params output_params;
  int r;
  user_state_callback_ret user_state;
  uint32_t latency_frames = 0;

  r = common_init(&ctx, "Cubeb callback return value example");
  ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";

  std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
      ctx, cubeb_destroy);

  if ((direction == INPUT_ONLY || direction == DUPLEX) &&
      !can_run_audio_input_test(ctx)) {
    /* This test needs an available input device, skip it if this host does not
     * have one or if the backend doesn't implement input. */
    return;
  }

  // Setup all params, but only pass them later as required by direction
  input_params.format = SAMPLE_FORMAT;
  input_params.rate = SAMPLE_FREQUENCY;
  input_params.channels = 1;
  input_params.layout = CUBEB_LAYOUT_MONO;
  input_params.prefs = CUBEB_STREAM_PREF_NONE;
  output_params = input_params;

  r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
  if (r != CUBEB_OK) {
    // not fatal
    latency_frames = 1024;
  }

  switch (direction) {
  case INPUT_ONLY:
    r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret input", NULL,
                          &input_params, NULL, NULL, latency_frames, data_cb,
                          state_cb_ret, &user_state);
    break;
  case OUTPUT_ONLY:
    r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret output", NULL, NULL,
                          NULL, &output_params, latency_frames, data_cb,
                          state_cb_ret, &user_state);
    break;
  case DUPLEX:
    r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret duplex", NULL,
                          &input_params, NULL, &output_params, latency_frames,
                          data_cb, state_cb_ret, &user_state);
    break;
  default:
    ASSERT_TRUE(false) << "Unrecognized test direction!";
  }
  EXPECT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";

  std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
      cleanup_stream_at_exit(stream, cubeb_stream_destroy);

  cubeb_stream_start(stream);
  delay(100);
  cubeb_stream_stop(stream);

  ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count)
      << "Callback called unexpected number of times for " << test_desc << "!";
  // TODO: On some test configurations, the data_callback is never called.
  if (data_cb == data_cb_ret_error && user_state.cb_count != 0) {
    ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state";
  }
}

TEST(cubeb, test_input_callback)
{
  run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0");
  run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one,
                    "input only, return nframes - 1");
  run_test_callback(INPUT_ONLY, data_cb_ret_nframes,
                    "input only, return nframes");
  run_test_callback(INPUT_ONLY, data_cb_ret_error,
                    "input only, return CUBEB_ERROR");
}

TEST(cubeb, test_output_callback)
{
  run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0");
  run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one,
                    "output only, return nframes - 1");
  run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes,
                    "output only, return nframes");
  run_test_callback(OUTPUT_ONLY, data_cb_ret_error,
                    "output only, return CUBEB_ERROR");
}

TEST(cubeb, test_duplex_callback)
{
  run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0");
  run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one,
                    "duplex, return nframes - 1");
  run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes");
  run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR");
}