-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathqaptest.c
2741 lines (2222 loc) · 73.7 KB
/
qaptest.c
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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <complex.h>
#include <fftw3.h>
#define MUNIT_ENABLE_ASSERT_ALIASES
#include "munit.h"
#include "qd.h"
static const char *
resolve_test_file(const char *filename)
{
static char buf[PATH_MAX];
const char *tests_dir;
tests_dir = getenv("TESTS_DIR");
if (!tests_dir)
tests_dir = ".";
snprintf(buf, sizeof (buf), "%s/%s", tests_dir, filename);
return buf;
}
struct input_desc {
const char *alias;
enum qd_input_id input_id;
const char *format;
const char *url;
};
static const struct input_desc *
find_input(const struct input_desc *inputs, const char *alias)
{
for (const struct input_desc *d = inputs; d->alias; d++) {
if (!strcmp(d->alias, alias))
return d;
}
return NULL;
}
struct file_alias {
const char *alias;
const char *filename;
};
static const char *
find_filename(const struct file_alias *files, const char *alias)
{
for (const struct file_alias *f = files; f->alias; f++) {
if (!strcmp(f->alias, alias))
return resolve_test_file(f->filename);
}
return NULL;
}
static inline bool
int16_is_silence(int16_t *samples, size_t count)
{
for (size_t i = 0; i < count; i++) {
if (samples[i] > 15 || samples[i] < -15)
return false;
}
return true;
}
/*
* Helper to measure peak frequency and gain of a single channel of audio data
*/
enum window {
WIN_RECT = 0,
WIN_HANN,
WIN_HAMMING,
};
static double hann(double v, unsigned int n)
{
return 0.5 * (1.0 - cos(2.0 * M_PI * v / (double)(n - 1)));
}
static double hamming(double v, unsigned int n)
{
return 0.54 - 0.46 * cos(2.0 * M_PI * v / (double)(n - 1));
}
struct peak_analyzer {
int sample_rate;
size_t n_samples;
size_t max_samples;
enum window window;;
double *rdata;
complex double *idata;
fftw_plan plan;
};
static void
peak_analyzer_cleanup(struct peak_analyzer *pa)
{
free(pa->plan);
free(pa->rdata);
free(pa->idata);
}
static int
peak_analyzer_init(struct peak_analyzer *pa, int sample_rate, size_t n_samples,
enum window window)
{
memset(pa, 0, sizeof (*pa));
pa->sample_rate = sample_rate;
pa->max_samples = n_samples;
pa->rdata = fftw_malloc(n_samples * sizeof (*pa->rdata));
pa->idata = fftw_malloc(n_samples * sizeof (*pa->idata));
pa->window = window;
if (!pa->rdata || !pa->idata)
goto fail;
pa->plan = fftw_plan_dft_r2c_1d(n_samples, pa->rdata, pa->idata,
FFTW_ESTIMATE);
if (!pa->plan)
goto fail;
return 0;
fail:
peak_analyzer_cleanup(pa);
return -1;
}
static size_t
peak_analyzer_add_samples(struct peak_analyzer *pa,
const int16_t *samples, size_t n_samples)
{
size_t avail;
avail = pa->max_samples - pa->n_samples;
n_samples = QD_MIN(n_samples, avail);
for (size_t i = 0; i < n_samples; i++) {
double d = samples[i] / 32768.0;
switch (pa->window) {
case WIN_RECT:
break;
case WIN_HANN:
d *= hann(pa->n_samples, pa->max_samples);
break;
case WIN_HAMMING:
d *= hamming(pa->n_samples, pa->max_samples);
break;
}
pa->rdata[pa->n_samples++] = d;
}
return n_samples;
}
static bool
peak_analyzer_run(struct peak_analyzer *pa,
double *out_peak_freq,
double *out_peak_gain)
{
double peak_freq = 0, peak_gain = -INFINITY;
int n_samples;
assert(pa->n_samples == pa->max_samples);
fftw_execute(pa->plan);
n_samples = pa->n_samples / 2;
for (int i = 1; i < n_samples; i++) {
double sq, gain;
complex double di = pa->idata[i];
/* normalize fft data */
di /= n_samples;
sq = creal(di) * creal(di) + cimag(di) * cimag(di);
gain = 10 * log10(sq);
if (gain > peak_gain) {
peak_gain = gain;
peak_freq = i * pa->sample_rate / (double)pa->n_samples;
}
}
if (peak_gain < -60.0)
return false;
if (out_peak_freq)
*out_peak_freq = peak_freq;
if (out_peak_gain)
*out_peak_gain = peak_gain;
return true;
}
//*****************************************************************************
// MS12 Tests
//*****************************************************************************
/* common parameters */
static char *parm_ms12_sessions_all[] = {
"ott", "broadcast", NULL
};
static char *parm_ms12_sessions_ott_only[] = {
"ott", NULL
};
static char *parm_ms12_outputs_pcm_single[] = {
"2.0", "5.1", "7.1", NULL
};
static char *parm_ms12_outputs_pcm_all[] = {
"2.0", "5.1", "7.1", "2.0+5.1", "2.0+7.1", NULL
};
static char *parm_ms12_outputs_all[] = {
"2.0", "5.1", "7.1", "ac3", "eac3",
"2.0+5.1", "2.0+7.1", "2.0+ac3", "2.0+eac3", NULL
};
static char *parm_ms12_outputs_pcm_stereo[] = {
"2.0", NULL
};
/*
* MS12 testing helpers
*/
/* common test prologue, initializing qd at INFO debug level */
static void *
pretest_ms12(const MunitParameter params[], void* user_data)
{
switch (munit_log_level_visible) {
case MUNIT_LOG_ERROR:
case MUNIT_LOG_WARNING:
qd_debug_level = 1;
break;
case MUNIT_LOG_INFO:
qd_debug_level = 2;
break;
case MUNIT_LOG_DEBUG:
qd_debug_level = 3;
break;
}
qd_init();
return NULL;
}
/* setup an MS12 session with common input parameters:
* - session type
* - outputs configuration
*/
static struct qd_session *
setup_ms12_session(const MunitParameter params[])
{
struct qd_session *session;
qap_session_t session_type = QAP_SESSION_BROADCAST;
enum qd_output_id outputs[12] = {};
int n_outputs = 0;
const char *v;
char *dump_path;
size_t n;
v = munit_parameters_get(params, "t");
if (!strcmp(v, "broadcast"))
session_type = QAP_SESSION_BROADCAST;
else if (!strcmp(v, "ott"))
session_type = QAP_SESSION_MS12_OTT;
else if (!strcmp(v, "decode"))
session_type = QAP_SESSION_DECODE_ONLY;
v = munit_parameters_get(params, "o");
while (*v) {
enum qd_output_id id = QD_OUTPUT_NONE;
n = strcspn(v, "+");
if (n == 0) {
v++;
continue;
}
if (!strncmp(v, "2.0", n))
id = QD_OUTPUT_STEREO;
else if (!strncmp(v, "5.1", n))
id = QD_OUTPUT_5DOT1;
else if (!strncmp(v, "7.1", n))
id = QD_OUTPUT_7DOT1;
else if (!strncmp(v, "ac3", n))
id = QD_OUTPUT_AC3_DECODED;
else if (!strncmp(v, "eac3", n))
id = QD_OUTPUT_EAC3_DECODED;
v += n;
/* ignore 7.1 output in OTT mode */
if (session_type == QAP_SESSION_MS12_OTT &&
id == QD_OUTPUT_7DOT1)
return NULL;
outputs[n_outputs++] = id;
}
assert_not_null((session = qd_session_create(QD_MODULE_DOLBY_MS12,
session_type)));
assert_int(qd_session_configure_outputs(session, n_outputs,
outputs), ==, 0);
if ((dump_path = getenv("DUMP_DIR")) != NULL) {
char buf[PATH_MAX];
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
snprintf(buf, sizeof (buf), "%s/qaptest-%lu%03lu",
dump_path, ts.tv_sec, ts.tv_nsec / 1000000);
qd_session_set_dump_path(session, buf);
}
return session;
}
/*
* MS12: test runtime input channel config changes
*
* Input files include multiple channel configuration, changing every few
* seconds. Verify the sequence of reported input configurations matches the
* actual data in the files. Each reported configuration is recorded and the
* result is checked after the full file has been played out.
*/
static const qap_input_config_t chid_swp_dd_configs[] = {
{ .channels = 1, .ch_map = { QAP_AUDIO_PCM_CHANNEL_C } },
{ .channels = 2, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R } },
{ .channels = 3, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_MS } },
{ .channels = 4, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_LS,
QAP_AUDIO_PCM_CHANNEL_RS } },
{ .channels = 3, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_C,
QAP_AUDIO_PCM_CHANNEL_R } },
{ .channels = 4, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_C,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_MS } },
{ .channels = 5, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_C,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_LS,
QAP_AUDIO_PCM_CHANNEL_RS } },
{ .channels = 6, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_C,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_LS,
QAP_AUDIO_PCM_CHANNEL_RS,
QAP_AUDIO_PCM_CHANNEL_LFE } },
};
static const qap_input_config_t chid_swp_aac_configs[] = {
{ .channels = 1, .ch_map = { QAP_AUDIO_PCM_CHANNEL_C } },
{ .channels = 2, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R } },
{ .channels = 3, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_CS } },
{ .channels = 4, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_LB,
QAP_AUDIO_PCM_CHANNEL_RB } },
{ .channels = 3, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_C } },
{ .channels = 4, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_C,
QAP_AUDIO_PCM_CHANNEL_CS } },
{ .channels = 5, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_C,
QAP_AUDIO_PCM_CHANNEL_LB,
QAP_AUDIO_PCM_CHANNEL_RB } },
{ .channels = 6, .ch_map = { QAP_AUDIO_PCM_CHANNEL_L,
QAP_AUDIO_PCM_CHANNEL_R,
QAP_AUDIO_PCM_CHANNEL_C,
QAP_AUDIO_PCM_CHANNEL_LFE,
QAP_AUDIO_PCM_CHANNEL_LB,
QAP_AUDIO_PCM_CHANNEL_RB } },
};
struct chid_swp_file {
const char *alias;
const char *filename;
int format;
int profile;
int n_configs;
const qap_input_config_t *configs;
};
static const struct chid_swp_file chid_swp_files[] = {
{ "dd",
"Elementary_Streams/ChID/ChID_voices/ChID_voices_swp_dd.ac3",
QAP_AUDIO_FORMAT_AC3, 0,
QD_N_ELEMENTS(chid_swp_dd_configs), chid_swp_dd_configs },
{ "ddp",
"Elementary_Streams/ChID/ChID_voices/ChID_voices_swp_ddp.ec3",
QAP_AUDIO_FORMAT_EAC3, 0,
QD_N_ELEMENTS(chid_swp_dd_configs), chid_swp_dd_configs },
{ "aac_adts",
"Elementary_Streams/ChID/ChID_voices/ChID_voices_swp_heaac.adts",
QAP_AUDIO_FORMAT_AAC_ADTS, QAP_PROFILE_AAC_LOW_COMPLEXITY,
QD_N_ELEMENTS(chid_swp_aac_configs), chid_swp_aac_configs },
{ "aac_loas",
"Elementary_Streams/ChID/ChID_voices/ChID_voices_swp_heaac.loas",
QAP_AUDIO_FORMAT_AAC_ADTS, QAP_PROFILE_AAC_MAIN,
QD_N_ELEMENTS(chid_swp_aac_configs), chid_swp_aac_configs
},
};
struct channel_sweep_ctx {
int n_configs;
const struct chid_swp_file *f;
};
static void
input_event_cb_record(struct qd_input *input, enum qd_input_event ev,
void *userdata)
{
struct channel_sweep_ctx *ctx = userdata;
int i;
if (ev != QD_INPUT_CONFIG_CHANGED)
return;
/* check we are not receiving more input reconfigs than expected */
assert_int(ctx->n_configs, <, ctx->f->n_configs);
/* check the input config matches the file description */
i = ctx->n_configs++;
assert_int(input->config.format, ==, ctx->f->format);
assert_int(input->config.profile, ==, ctx->f->profile);
assert_int(input->config.channels, ==, ctx->f->configs[i].channels);
assert_memory_equal(sizeof (input->config.ch_map),
input->config.ch_map, ctx->f->configs[i].ch_map);
}
static MunitResult
test_ms12_channel_sweep(const MunitParameter params[],
void *user_data_or_fixture)
{
struct qd_session *session;
struct qd_input *input;
struct ffmpeg_src *src;
struct channel_sweep_ctx ctx = {};
const char *v;
if (!(session = setup_ms12_session(params)))
return MUNIT_SKIP;
v = munit_parameters_get(params, "f");
for (size_t i = 0; i < QD_N_ELEMENTS(chid_swp_files); i++) {
if (!strcmp(v, chid_swp_files[i].alias))
ctx.f = &chid_swp_files[i];
}
if (!ctx.f)
return MUNIT_ERROR;
assert_not_null((src = ffmpeg_src_create(resolve_test_file(ctx.f->filename), NULL)));
assert_not_null((input = ffmpeg_src_add_input(src, 0, session, QD_INPUT_MAIN)));
qd_input_set_event_cb(input, input_event_cb_record, &ctx);
assert_int(0, ==, ffmpeg_src_thread_start(src));
assert_int(0, ==, ffmpeg_src_thread_join(src));
assert_int(0, ==, ffmpeg_src_wait_eos(src, false, 1 * QD_SECOND));
/* check we received the expected number of input configuration events */
assert_int(ctx.n_configs, ==, ctx.f->n_configs);
ffmpeg_src_destroy(src);
qd_session_destroy(session);
return MUNIT_OK;
}
static char *parm_ms12_files_channel_sweep[] = {
"dd", "ddp", "aac_adts", "aac_loas", NULL
};
static MunitParameterEnum parms_ms12_channel_sweep[] = {
{ "t", parm_ms12_sessions_all },
{ "o", parm_ms12_outputs_pcm_all },
{ "f", parm_ms12_files_channel_sweep },
{ NULL, NULL },
};
/*
* MS12: test Main+Assoc mixing
*
* Input files feature a single channel 997Hz tone at -20dBFS. Main is in left
* channel and Assoc is in right channel.
*
* Compute an FFT on 1s chunks of audio output data and verify we get the
* correct peak frequency and gain, testing at multiple "xu" kvpairs values.
*/
struct assoc_mix_ctx {
struct {
struct peak_analyzer pa[2];
} outputs[QD_MAX_OUTPUTS];
int gain[2];
};
static void
assoc_mix_output_cb(struct qd_output *output, qap_audio_buffer_t *buffer,
void *userdata)
{
struct assoc_mix_ctx *ctx = userdata;
struct peak_analyzer *pa;
size_t frame_size;
if (!qd_format_is_pcm(output->config.format))
return;
if (output->pts > 8 * QD_SECOND)
return;
/* check output config */
assert_int(output->config.sample_rate, ==, 48000);
assert_int(output->config.bit_width, ==, 16);
frame_size = output->config.channels * output->config.bit_width / 8;
assert_int(buffer->common_params.size % frame_size, ==, 0);
/* feed left and right channel data */
pa = ctx->outputs[output->id].pa;
for (size_t i = 0; i < buffer->common_params.size; i += frame_size) {
int16_t *frame = buffer->common_params.data + i;
peak_analyzer_add_samples(&pa[0], frame + 0, 1);
peak_analyzer_add_samples(&pa[1], frame + 1, 1);
}
for (int i = 0; i < 2; i++) {
double freq, gain;
bool mute;
/* fill window before running fft and analyzing data */
if (pa[i].n_samples != pa[i].max_samples)
continue;
/* verify peak frequency and gain are correct */
mute = ctx->gain[i] <= -32;
assert_true(mute == !peak_analyzer_run(&pa[i], &freq, &gain));
if (!mute) {
assert_double(freq, ==, 997);
assert_double(gain, >, -24 + ctx->gain[i] - 1.0);
assert_double(gain, <, -24 + ctx->gain[i] + 1.0);
}
/* reset window */
pa[i].n_samples = 0;
}
}
static const struct file_alias assoc_mix_main_files[] = {
{ "ddp", "Elementary_Streams/Mix_Fader/Mix_fader_neutral_2PID_ddp_main.ec3" },
{ "aac", "Elementary_Streams/Mix_Fader/Mix_fader_neutral_2PID_heaac_main.loas" },
{ NULL, NULL }
};
static const struct file_alias assoc_mix_assoc_files[] = {
{ "ddp", "Elementary_Streams/Mix_Fader/Mix_fader_neutral_2PID_ddp_assoc.ec3" },
{ "aac", "Elementary_Streams/Mix_Fader/Mix_fader_neutral_2PID_heaac_assoc.loas" },
{ NULL, NULL }
};
static MunitResult
test_ms12_assoc_mix(const MunitParameter params[],
void *user_data_or_fixture)
{
struct qd_session *session;
struct qd_input *input_main, *input_assoc;
struct ffmpeg_src *src_main, *src_assoc;
const char *v, *f_main, *f_assoc;
struct assoc_mix_ctx ctx;
int xu;
if (!(session = setup_ms12_session(params)))
return MUNIT_SKIP;
qd_session_set_output_cb(session, assoc_mix_output_cb, &ctx);
/* setup fft to determine peak freq/gain, with a 1s window */
for (size_t i = 0; i < QD_N_ELEMENTS(ctx.outputs); i++) {
for (size_t j = 0; j < QD_N_ELEMENTS(ctx.outputs[i].pa); j++)
peak_analyzer_init(&ctx.outputs[i].pa[j],
48000, 48000, WIN_RECT);
}
/* set main/assoc mixing gain */
v = munit_parameters_get(params, "xu");
xu = v ? atoi(v) : 0;
ctx.gain[0] = xu < 0 ? xu : 0;
ctx.gain[1] = xu > 0 ? -xu : 0;
qd_session_set_kvpairs(session, "xu=%d", xu);
/* create main input */
v = munit_parameters_get(params, "f");
if (!(f_main = find_filename(assoc_mix_main_files, v)))
return MUNIT_ERROR;
assert_not_null((src_main = ffmpeg_src_create(f_main, NULL)));
assert_not_null((input_main = ffmpeg_src_add_input(src_main,
0, session,
QD_INPUT_MAIN)));
/* create assoc input */
v = munit_parameters_get(params, "f");
if (!(f_assoc = find_filename(assoc_mix_assoc_files, v)))
return MUNIT_ERROR;
assert_not_null((src_assoc = ffmpeg_src_create(f_assoc, NULL)));
assert_not_null((input_assoc = ffmpeg_src_add_input(src_assoc,
0, session,
QD_INPUT_ASSOC)));
/* skip silence and ref tone at beginning of input files */
assert_int(0, ==, ffmpeg_src_seek(src_main, 35000));
assert_int(0, ==, ffmpeg_src_seek(src_assoc, 35000));
/* skip 1s of output data, to skip audio ramping up */
qd_session_set_output_discard_ms(session, 1000);
/* play */
assert_int(0, ==, ffmpeg_src_thread_start(src_main));
assert_int(0, ==, ffmpeg_src_thread_start(src_assoc));
/* drain main */
assert_int(0, ==, ffmpeg_src_thread_join(src_main));
assert_int(0, ==, ffmpeg_src_wait_eos(src_main, false,
1 * QD_SECOND));
/* drain assoc */
ffmpeg_src_thread_stop(src_assoc);
ffmpeg_src_thread_join(src_assoc);
assert_int(0, ==, ffmpeg_src_wait_eos(src_assoc, false,
1 * QD_SECOND));
ffmpeg_src_destroy(src_main);
ffmpeg_src_destroy(src_assoc);
qd_session_destroy(session);
for (size_t i = 0; i < QD_N_ELEMENTS(ctx.outputs); i++) {
for (size_t j = 0; j < QD_N_ELEMENTS(ctx.outputs[i].pa); j++)
peak_analyzer_cleanup(&ctx.outputs[i].pa[j]);
}
return MUNIT_OK;
}
static char *parm_ms12_files_assoc_mix[] = {
"ddp", "aac", NULL
};
static char *parm_ms12_xu_assoc_mix[] = {
"0", "-16", "16", "-32", "32", NULL
};
static MunitParameterEnum parms_ms12_assoc_mix[] = {
{ "t", parm_ms12_sessions_all },
{ "o", parm_ms12_outputs_all },
{ "f", parm_ms12_files_assoc_mix },
{ "xu", parm_ms12_xu_assoc_mix },
{ NULL, NULL },
};
/*
* MS12: test Main+Assoc mixing with a gap in the assoc stream
*
* Verify there is no more than 1s of silence in the output stream when the
* assoc stream is feeding data anymore. Main input must continue to be decoded
* in this case.
*/
struct assoc_disappearing_output {
int l_silent_frame_count;
int r_silent_frame_count;
bool seen_l_silence;
bool seen_r_silence;
};
struct assoc_disappearing_ctx {
struct assoc_disappearing_output outputs[QD_MAX_OUTPUTS];
};
static void
assoc_disappearing_output_cb(struct qd_output *output, qap_audio_buffer_t *buffer,
void *userdata)
{
struct assoc_disappearing_ctx *ctx = userdata;
struct assoc_disappearing_output *out = &ctx->outputs[output->id];
size_t frame_size;
/* check output config */
assert_int(output->config.sample_rate, ==, 48000);
assert_int(output->config.bit_width, ==, 16);
assert_int(output->config.channels, >=, 2);
frame_size = output->config.channels * output->config.bit_width / 8;
assert_int(buffer->common_params.size % frame_size, ==, 0);
/* discard early audio, AAC and DDP outputs are not identical */
if (output->pts < 2.5 * QD_SECOND)
return;
for (size_t i = 0; i < buffer->common_params.size; i += frame_size) {
int16_t *frame = buffer->common_params.data + i;
/* record number of contiguous silent samples for left and
* right channels */
if (int16_is_silence(frame + 0, 1))
out->l_silent_frame_count++;
else
out->l_silent_frame_count = 0;
if (int16_is_silence(frame + 1, 1))
out->r_silent_frame_count++;
else
out->r_silent_frame_count = 0;
if (output->pts < 9.9 * QD_SECOND ||
(output->pts > 20.9 * QD_SECOND &&
output->pts < 29.8 * QD_SECOND)) {
/* silence is not allowed in L/R when Assoc is fed */
assert_int(out->l_silent_frame_count, <, 48);
/* ignore aac cert file error at ~24s */
if (output->pts < 23 * QD_SECOND &&
output->pts > 25 * QD_SECOND)
assert_int(out->r_silent_frame_count, <, 48);
} else {
/* check that we are still seeing main audio when Assoc
* is not fed */
assert_int(out->l_silent_frame_count, <, 3 * 48000);
assert_int(out->r_silent_frame_count, <, 3 * 48000);
}
/* record if we've seen at least 2s of silence */
if (out->l_silent_frame_count > 2 * 48000)
out->seen_l_silence = true;
if (out->r_silent_frame_count > 2 * 48000)
out->seen_r_silence = true;
/* verify other channels are silent */
assert_true(int16_is_silence(frame + 2,
output->config.channels - 2));
}
}
static const struct file_alias assoc_disappearing_files[] = {
{ "ddp", "Transport_Streams/DVB_h264_25fps/DisappearingAA/DD_Disappearing-AA_ddp_DVB_h264_25fps.trp" },
{ "aac", "Transport_Streams/DVB_h264_25fps/DisappearingAA/DD_Disappearing-AA_heaac_DVB_h264_25fps.trp" },
{ NULL, NULL }
};
static MunitResult
test_ms12_assoc_disappearing(const MunitParameter params[],
void *user_data_or_fixture)
{
struct qd_session *session;
struct qd_input *input_main, *input_assoc;
struct ffmpeg_src *src;
const char *v, *f;
struct assoc_disappearing_ctx ctx = {};
if (!(session = setup_ms12_session(params)))
return MUNIT_SKIP;
qd_session_set_output_cb(session, assoc_disappearing_output_cb, &ctx);
/* respect input timestamps so that Main and Assoc are synchronized,
* even in OTT mode */
qd_session_ignore_timestamps(session, false);
/* create source from TS file */
v = munit_parameters_get(params, "f");
if (!(f = find_filename(assoc_disappearing_files, v)))
return MUNIT_ERROR;
assert_not_null((src = ffmpeg_src_create(f, NULL)));
/* create main input from main stream */
assert_not_null((input_main = ffmpeg_src_add_input(src, 1, session,
QD_INPUT_MAIN)));
/* create assoc input from second stream */
assert_not_null((input_assoc = ffmpeg_src_add_input(src, 2, session,
QD_INPUT_ASSOC)));
/* play */
assert_int(0, ==, ffmpeg_src_thread_start(src));
assert_int(0, ==, ffmpeg_src_thread_join(src));
/* drain output */
ffmpeg_src_wait_eos(src, false, 1 * QD_SECOND);
/* verify we've seen at least 2s of silence in left and right channels,
* which is expected while Assoc has "disappeared" */
for (int i = 0; i < QD_MAX_OUTPUTS; i++) {
if (session->outputs[i].enabled) {
assert_true(ctx.outputs[i].seen_l_silence);
assert_true(ctx.outputs[i].seen_r_silence);
}
}
ffmpeg_src_destroy(src);
qd_session_destroy(session);
return MUNIT_OK;
}
static char *parm_ms12_files_assoc_disappearing[] = {
"ddp", "aac", NULL
};
static MunitParameterEnum parms_ms12_assoc_disappearing[] = {
{ "t", parm_ms12_sessions_all },
{ "o", parm_ms12_outputs_pcm_all },
{ "f", parm_ms12_files_assoc_disappearing },
{ NULL, NULL },
};
/*
* MS12: test Main+Main2 mixing
*
* Input files feature a single channel 997Hz tone at -20dBFS. Main is in left
* channel and Main2 is in right channel.
*
* Compute an FFT on 1s chunks of audio output data and verify we get the
* correct peak frequency and gain, testing at multiple "main1_mixgain" and
* "main2_mixgain" kvpairs values.
*/
struct main2_mix_ctx {
struct {
struct peak_analyzer pa[2];
} outputs[QD_MAX_OUTPUTS];
int gain;
};
static void
main2_mix_output_cb(struct qd_output *output, qap_audio_buffer_t *buffer,
void *userdata)
{
struct main2_mix_ctx *ctx = userdata;
struct peak_analyzer *pa;
size_t frame_size;
/* check output config */
assert_int(output->config.sample_rate, ==, 48000);
assert_int(output->config.bit_width, ==, 16);
frame_size = output->config.channels * output->config.bit_width / 8;
assert_int(buffer->common_params.size % frame_size, ==, 0);
/* feed left and right channel data */
pa = ctx->outputs[output->id].pa;
for (size_t i = 0; i < buffer->common_params.size; i += frame_size) {
int16_t *frame = buffer->common_params.data + i;
peak_analyzer_add_samples(&pa[0], frame + 0, 1);
peak_analyzer_add_samples(&pa[1], frame + 1, 1);
}
for (int i = 0; i < 2; i++) {
double freq, gain;
/* fill window before running fft and analyzing data */
if (pa[i].n_samples != pa[i].max_samples)
continue;
/* verify peak frequency and gain are correct */
assert_true(peak_analyzer_run(&pa[i], &freq, &gain));
assert_double(freq, ==, 997);
assert_double(gain, >, -20 + ctx->gain - 1.0);
assert_double(gain, <, -20 + ctx->gain + 1.0);
/* reset window */
pa[i].n_samples = 0;
}
}
static const struct file_alias main2_mix_main_files[] = {
{ "ddp", "Elementary_Streams/Mix_Fader/Mix_fader_neutral_2PID_ddp_main.ec3" },
{ NULL, NULL }
};
static const struct file_alias main2_mix_main2_files[] = {
{ "ddp", "Elementary_Streams/Mix_Fader/Mix_fader_neutral_2PID_ddp_assoc.ec3" },
{ NULL, NULL }
};
static MunitResult
test_ms12_main2_mix(const MunitParameter params[],
void *user_data_or_fixture)
{
struct qd_session *session;
struct qd_input *input_main, *input_main2;
struct ffmpeg_src *src_main, *src_main2;
const char *v, *f_main, *f_main2;
struct main2_mix_ctx ctx;
if (!(session = setup_ms12_session(params)))
return MUNIT_SKIP;
qd_session_set_output_cb(session, main2_mix_output_cb, &ctx);
/* setup fft to determine peak freq/gain, with a 1s window */
for (size_t i = 0; i < QD_N_ELEMENTS(ctx.outputs); i++) {
for (size_t j = 0; j < QD_N_ELEMENTS(ctx.outputs[i].pa); j++)
peak_analyzer_init(&ctx.outputs[i].pa[j],
48000, 48000, WIN_RECT);
}
/* set main/main2 mixing gain */
v = munit_parameters_get(params, "main_mixgain");
qd_session_set_kvpairs(session, "main1_mixgain=%s;main2_mixgain=%s", v, v);
ctx.gain = atoi(v);
/* create main input */
v = munit_parameters_get(params, "f");
if (!(f_main = find_filename(main2_mix_main_files, v)))
return MUNIT_ERROR;
assert_not_null((src_main = ffmpeg_src_create(f_main, NULL)));
assert_not_null((input_main = ffmpeg_src_add_input(src_main,
0, session,
QD_INPUT_MAIN)));
/* create main2 input */
v = munit_parameters_get(params, "f");
if (!(f_main2 = find_filename(main2_mix_main2_files, v)))
return MUNIT_ERROR;
assert_not_null((src_main2 = ffmpeg_src_create(f_main2, NULL)));
assert_not_null((input_main2 = ffmpeg_src_add_input(src_main2,
0, session,
QD_INPUT_MAIN2)));
/* skip silence and ref tone at beginning of input files */
assert_int(0, ==, ffmpeg_src_seek(src_main, 35000));
assert_int(0, ==, ffmpeg_src_seek(src_main2, 35000));
/* skip 1s of output data, to skip audio ramping up */
qd_session_set_output_discard_ms(session, 1000);
/* play */
assert_int(0, ==, ffmpeg_src_thread_start(src_main));
assert_int(0, ==, ffmpeg_src_thread_start(src_main2));
assert_int(0, ==, ffmpeg_src_thread_join(src_main));
assert_int(0, ==, ffmpeg_src_thread_join(src_main2));
/* drain output */
ffmpeg_src_wait_eos(src_main, false, 1 * QD_SECOND);
ffmpeg_src_wait_eos(src_main2, false, 1 * QD_SECOND);
ffmpeg_src_destroy(src_main);
ffmpeg_src_destroy(src_main2);
qd_session_destroy(session);
for (size_t i = 0; i < QD_N_ELEMENTS(ctx.outputs); i++) {
for (size_t j = 0; j < QD_N_ELEMENTS(ctx.outputs[i].pa); j++)
peak_analyzer_cleanup(&ctx.outputs[i].pa[j]);
}
return MUNIT_OK;
}
static char *parm_ms12_files_main2_mix[] = {
"ddp", NULL
};
static char *parm_ms12_main_mixgain[] = {
"-10,0,0", "-16,0,0", NULL,
};
static MunitParameterEnum parms_ms12_main2_mix[] = {
{ "t", parm_ms12_sessions_ott_only },