Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021-2022, kleines Filmröllchen <filmroellchen@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/Types.h>
9#include <LibAudio/ConnectionToServer.h>
10#include <LibAudio/Loader.h>
11#include <LibAudio/Resampler.h>
12#include <LibCore/ArgsParser.h>
13#include <LibCore/DeprecatedFile.h>
14#include <LibCore/EventLoop.h>
15#include <LibCore/System.h>
16#include <LibMain/Main.h>
17#include <math.h>
18#include <stdio.h>
19
20// The Kernel has issues with very large anonymous buffers.
21// FIXME: This appears to be fine for now, but it's really a hack.
22constexpr size_t LOAD_CHUNK_SIZE = 128 * KiB;
23
24ErrorOr<int> serenity_main(Main::Arguments arguments)
25{
26 TRY(Core::System::pledge("stdio rpath sendfd unix thread proc"));
27
28 StringView path {};
29 bool should_loop = false;
30 bool show_sample_progress = false;
31
32 Core::ArgsParser args_parser;
33 args_parser.add_positional_argument(path, "Path to audio file", "path");
34 args_parser.add_option(should_loop, "Loop playback", "loop", 'l');
35 args_parser.add_option(show_sample_progress, "Show playback progress in samples", "sample-progress", 's');
36 args_parser.parse(arguments);
37
38 TRY(Core::System::unveil("/tmp/session/%sid/portal/audio", "rw"));
39 TRY(Core::System::unveil(Core::DeprecatedFile::absolute_path(path), "r"sv));
40 TRY(Core::System::unveil(nullptr, nullptr));
41
42 Core::EventLoop loop;
43
44 auto audio_client = TRY(Audio::ConnectionToServer::try_create());
45 auto maybe_loader = Audio::Loader::create(path);
46 if (maybe_loader.is_error()) {
47 warnln("Failed to load audio file: {}", maybe_loader.error().description);
48 return 1;
49 }
50 auto loader = maybe_loader.release_value();
51
52 TRY(Core::System::pledge("stdio sendfd thread proc"));
53
54 outln("\033[34;1m Playing\033[0m: {}", path);
55 outln("\033[34;1m Format\033[0m: {} {} Hz, {}-bit, {}",
56 loader->format_name(),
57 loader->sample_rate(),
58 loader->bits_per_sample(),
59 loader->num_channels() == 1 ? "Mono" : "Stereo");
60 out("\033[34;1mProgress\033[0m: \033[s");
61
62 auto resampler = Audio::ResampleHelper<Audio::Sample>(loader->sample_rate(), audio_client->get_sample_rate());
63
64 // If we're downsampling, we need to appropriately load more samples at once.
65 size_t const load_size = static_cast<size_t>(LOAD_CHUNK_SIZE * static_cast<double>(loader->sample_rate()) / static_cast<double>(audio_client->get_sample_rate()));
66
67 auto print_playback_update = [&]() {
68 out("\033[u");
69 if (show_sample_progress) {
70 out("{}/{}", audio_client->total_played_samples(), loader->total_samples());
71 } else {
72 auto playing_seconds = static_cast<int>(floor(static_cast<double>(audio_client->total_played_samples()) / static_cast<double>(loader->sample_rate())));
73 auto playing_minutes = playing_seconds / 60;
74 auto playing_seconds_of_minute = playing_seconds % 60;
75
76 auto total_seconds = static_cast<int>(floor(static_cast<double>(loader->total_samples()) / static_cast<double>(loader->sample_rate())));
77 auto total_minutes = total_seconds / 60;
78 auto total_seconds_of_minute = total_seconds % 60;
79
80 auto remaining_seconds = total_seconds - playing_seconds;
81 auto remaining_minutes = remaining_seconds / 60;
82 auto remaining_seconds_of_minute = remaining_seconds % 60;
83
84 out("\033[1m{:02d}:{:02d}\033[0m [{}{:02d}:{:02d}] -- {:02d}:{:02d}",
85 playing_minutes, playing_seconds_of_minute,
86 remaining_seconds == 0 ? " " : "-",
87 remaining_minutes, remaining_seconds_of_minute,
88 total_minutes, total_seconds_of_minute);
89 }
90 fflush(stdout);
91 };
92
93 for (;;) {
94 auto samples = loader->get_more_samples(load_size);
95 if (!samples.is_error()) {
96 if (samples.value().size() > 0) {
97 print_playback_update();
98 // We can read and enqueue more samples
99 resampler.reset();
100 auto resampled_samples = resampler.resample(move(samples.value()));
101 TRY(audio_client->async_enqueue(move(resampled_samples)));
102 } else if (should_loop) {
103 // We're done: now loop
104 auto result = loader->reset();
105 if (result.is_error()) {
106 outln();
107 outln("Error while resetting: {} (at {:x})", result.error().description, result.error().index);
108 }
109 } else if (samples.value().size() == 0 && audio_client->remaining_samples() == 0) {
110 // We're done and the server is done
111 break;
112 }
113 while (audio_client->remaining_samples() > load_size) {
114 // The server has enough data for now
115 print_playback_update();
116 usleep(1'000'000 / 10);
117 }
118 } else {
119 outln();
120 outln("Error: {} (at {:x})", samples.error().description, samples.error().index);
121 return 1;
122 }
123 }
124 outln();
125 return 0;
126}