diff --git a/tests/test_audio_compressor.c b/tests/test_audio_compressor.c index 15776d28..286d143d 100644 --- a/tests/test_audio_compressor.c +++ b/tests/test_audio_compressor.c @@ -244,40 +244,49 @@ static void test_streaming_matches_recording(void) { audio_compressor_destroy(recording); audio_compressor_destroy(streaming); } -/* Тракт трансляции: усиление текущего mono-кадра перед Opus, live bypass и смена предела gain. */ +/* Тракт трансляции: общий gain стереопары перед Opus, live bypass и смена предела gain. */ static void test_live_opus_gain(void) { struct audio_compressor* ac = audio_compressor_create(); - opus_codec_encoder_t* encoder = opus_codec_encoder_create(48000, 1); - opus_codec_decoder_t* decoder = opus_codec_decoder_create(48000, 1); + opus_codec_encoder_t* encoder = opus_codec_encoder_create(48000, 2); + opus_codec_decoder_t* decoder = opus_codec_decoder_create(48000, 2); TEST(ac && encoder && decoder, "live audio codecs created"); if (!ac || !encoder || !decoder) goto done; - audio_compressor_config_t cfg = {.sample_rate = 48000, .channels = 1, .block_duration_ms = 20, + audio_compressor_config_t cfg = {.sample_rate = 48000, .channels = 2, .block_duration_ms = 20, .lookback_ms = 200, .lookahead_ms = 0, .max_gain_db = 6, .rise_rate_per_sec = 6, .release_rate_per_sec = 30, .target_level = 0.25f}; TEST(audio_compressor_configure(ac, &cfg) == 0, "live radio compressor configuration"); TEST(opus_codec_encoder_application_set(encoder, OPUS_CODEC_APP_AUDIO) == 0, "live Opus audio mode"); - TEST(opus_codec_encoder_bitrate_set(encoder, 64000) == 0, "live Opus bitrate"); - int16_t input[960], compressed[960], decoded[960]; uint8_t packet[1275]; + TEST(opus_codec_encoder_bitrate_set(encoder, 128000) == 0, "live Opus bitrate"); + int16_t input[1920], compressed[1920], decoded[1920]; uint8_t packet[1275]; size_t allocations = u_get_allocated_count(); for (int frame = 0; frame < 30; frame++) { - for (unsigned i = 0; i < 960; i++) input[i] = (int16_t)(500 * sin((frame * 960 + i) * 2 * M_PI * 440 / 48000)); - TEST(audio_compressor_process_frame(ac, input, compressed, 960) == 0, "live current frame compressed"); + for (unsigned i = 0; i < 960; i++) { + input[2 * i] = (int16_t)(500 * sin((frame * 960 + i) * 2 * M_PI * 440 / 48000)); + input[2 * i + 1] = (int16_t)(125 * sin((frame * 960 + i) * 2 * M_PI * 1100 / 48000)); + } + TEST(audio_compressor_process_frame(ac, input, compressed, 1920) == 0, "live current stereo frame compressed"); int length = opus_codec_encode(encoder, compressed, 960, packet, sizeof(packet)); TEST(length > 0, "live compressed frame encoded"); if (length <= 0) goto done; TEST(opus_codec_decode(decoder, packet, length, decoded, 960) == 960, "live decoded packet has 20ms of PCM"); } double input_energy = 0, output_energy = 0; - for (unsigned i = 0; i < 960; i++) { input_energy += (double)input[i] * input[i]; output_energy += (double)decoded[i] * decoded[i]; } + for (unsigned i = 0; i < 1920; i++) { input_energy += (double)input[i] * input[i]; output_energy += (double)decoded[i] * decoded[i]; } TEST(output_energy > input_energy * 2.5 && output_energy < input_energy * 5, "gain survives real Opus roundtrip"); + double left_energy = 0, right_energy = 0; + for (unsigned i = 0; i < 960; i++) { + left_energy += (double)decoded[2 * i] * decoded[2 * i]; + right_energy += (double)decoded[2 * i + 1] * decoded[2 * i + 1]; + } + TEST(left_energy > right_energy * 8 && left_energy < right_energy * 25, "stereo balance survives compressor and Opus"); TEST(u_get_allocated_count() == allocations, "live audio does not accumulate compressor buffers"); audio_compressor_set_enabled(ac, 0); memcpy(compressed, input, sizeof(input)); - TEST(audio_compressor_process_frame(ac, compressed, compressed, 960) == 0, "live bypass in place"); + TEST(audio_compressor_process_frame(ac, compressed, compressed, 1920) == 0, "live bypass in place"); TEST(memcmp(input, compressed, sizeof(input)) == 0, "disabled compressor preserves current PCM exactly"); cfg.max_gain_db = 20; TEST(audio_compressor_configure(ac, &cfg) == 0, "live gain limit changed"); audio_compressor_set_enabled(ac, 1); - for (unsigned frame = 0; frame < 50; frame++) TEST(audio_compressor_process_frame(ac, input, compressed, 960) == 0, "live new gain frame"); + for (unsigned frame = 0; frame < 50; frame++) TEST(audio_compressor_process_frame(ac, input, compressed, 1920) == 0, "live new gain frame"); TEST(audio_compressor_gain_db(ac) > 19.9f && audio_compressor_gain_db(ac) <= 20.01f, "live new gain limit applied"); TEST(audio_compressor_output_size(ac) == 0, "live gain update introduces no output backlog"); done: diff --git a/tests/test_stream_media.c b/tests/test_stream_media.c index 3a4713f0..aabe02ad 100644 --- a/tests/test_stream_media.c +++ b/tests/test_stream_media.c @@ -177,13 +177,40 @@ static void test_revisions(void) { stream_media_receiver_destroy(r); } +static void stereo_frame(void *arg, const struct stream_media_format *format, + const struct stream_media_video *video, const struct stream_media_audio *audio) { + CHECK(!video && audio && audio->samples == 960 && format->audio_codec == STREAM_MEDIA_OPUS_STEREO); + (*(unsigned *)arg)++; +} + +/* Стерео обозначается в wire явно; 960 — число кадров на канал, трек не меняется внутри эпохи. */ +static void test_stereo(void) { + struct stream_media_block b = {.format = {1280, 720, 0, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS_STEREO, 1}, .audio_count = 1}; + b.audio[0] = (struct stream_media_audio){1000000, 3, 960, (const uint8_t *)"ops"}; + uint8_t wire[7080]; size_t length = 0; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + struct stream_media_block parsed; + CHECK(stream_media_parse(wire, length, &parsed) == STREAM_OK && parsed.format.audio_codec == STREAM_MEDIA_OPUS_STEREO); + unsigned received = 0; struct stream_media_receiver *receiver = NULL; + CHECK(stream_media_receiver_create(&receiver, stereo_frame, &received) == STREAM_OK); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && received == 1); + b.format.audio_codec = STREAM_MEDIA_OPUS; b.format.revision++; b.audio[0].pts_us += 20000; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_ERR_CONFLICT && received == 1); + b.format.audio_codec = STREAM_MEDIA_OPUS_STEREO; b.audio[0].samples = 1920; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_ERR_FORMAT); + b.audio[0].samples = 960; b.format.audio_codec = 3; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_ERR_FORMAT); + stream_media_receiver_destroy(receiver); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); /* Усечения проверяются массово; ожидаемые отказы не загромождают диагностический лог. */ debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_DISABLED); size_t before = u_get_allocated_count(); for (unsigned i = 0; i < sizeof(expected_video); i++) expected_video[i] = (uint8_t)(i * 31 + 7); - test_media(); test_batches(); test_revisions(); + test_media(); test_batches(); test_revisions(); test_stereo(); CHECK(u_get_allocated_count() == before); debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_INFO); DEBUG_INFO(DEBUG_CATEGORY_STREAM, "PASS stream_media: protocol, FEC, frame recovery, boundaries, no leaks");