29#include <libavcodec/avcodec.h>
30#include <libavformat/avformat.h>
31#include <libavutil/opt.h>
32#include <libswresample/swresample.h>
35static AVFormatContext *
fmtCtx =
nullptr;
61 bool open(
const float *samples, std::size_t sample_count,
int output_device) {
63 if (samples ==
nullptr || sample_count == 0)
67 const std::vector<unsigned int> device_ids =
audio.getDeviceIds();
68 if (device_ids.empty()) {
69 std::cerr <<
"acmx2: file_audio: No audio output devices found\n";
73 const unsigned int device =
75 ?
static_cast<unsigned int>(output_device)
76 :
audio.getDefaultOutputDevice();
77 const RtAudio::DeviceInfo info =
audio.getDeviceInfo(device);
78 if (info.outputChannels == 0) {
79 std::cerr <<
"acmx2: file_audio: Selected device has no output channels\n";
91 RtAudio::StreamParameters output_parameters;
92 output_parameters.deviceId = device;
94 output_parameters.firstChannel = 0;
96 unsigned int buffer_frames = 512;
97 audio.openStream(&output_parameters,
nullptr, RTAUDIO_FLOAT32,
101 std::cout <<
"acmx2: file_audio: Playback configured on device "
105 }
catch (
const std::exception &error) {
106 std::cerr <<
"acmx2: file_audio: Could not open output stream: "
107 << error.what() <<
"\n";
117 active.store(
true, std::memory_order_release);
118 started.store(
true, std::memory_order_release);
120 std::cout <<
"acmx2: file_audio: Playback started\n";
122 }
catch (
const std::exception &error) {
123 active.store(
false, std::memory_order_release);
124 started.store(
false, std::memory_order_release);
125 std::cerr <<
"acmx2: file_audio: Could not start output stream: "
126 << error.what() <<
"\n";
132 active.store(
false, std::memory_order_release);
133 if (
audio.isStreamOpen()) {
135 if (
audio.isStreamRunning())
138 }
catch (
const std::exception &error) {
139 std::cerr <<
"acmx2: file_audio: Error closing output stream: "
140 << error.what() <<
"\n";
144 started.store(
false, std::memory_order_release);
156 return started.load(std::memory_order_acquire);
172 if (std::find(rates.begin(), rates.end(), 48000U) != rates.end())
174 return rates.front();
178 double, RtAudioStreamStatus,
void *user_data) {
180 ->
write_samples(
static_cast<float *
>(output_buffer), frame_count);
184 if (output ==
nullptr)
187 const double source_step =
190 for (
unsigned int frame = 0; frame < frame_count; ++frame) {
192 if (
active.load(std::memory_order_relaxed) &&
199 active.store(
false, std::memory_order_release);
206 const std::size_t next_index =
210 const float fraction =
217 active.store(
false, std::memory_order_release);
227 std::memory_order_release);
230 std::memory_order_release);
258 avformat_close_input(&
fmtCtx);
263 const std::string whitespace =
" \t\r\n";
264 const std::size_t first = line.find_first_not_of(whitespace);
265 if (first == std::string::npos)
267 const std::size_t last = line.find_last_not_of(whitespace);
268 return line.substr(first, last - first + 1);
272 std::string extension = std::filesystem::path(filepath).extension().string();
273 std::transform(extension.begin(), extension.end(), extension.begin(),
274 [](
unsigned char value) {
275 return static_cast<char>(std::tolower(value));
277 return extension ==
".m3u" || extension ==
".m3u8";
281 std::ifstream input(filepath);
283 std::cerr <<
"acmx2: file_audio: Cannot open M3U playlist: "
288 const std::filesystem::path playlistDirectory =
289 std::filesystem::absolute(std::filesystem::path(filepath)).parent_path();
290 std::vector<std::string> paths;
292 bool firstLine =
true;
293 while (std::getline(input, line)) {
294 if (firstLine && line.size() >= 3 &&
295 static_cast<unsigned char>(line[0]) == 0xef &&
296 static_cast<unsigned char>(line[1]) == 0xbb &&
297 static_cast<unsigned char>(line[2]) == 0xbf) {
302 if (line.empty() || line.front() ==
'#')
305 if (line.find(
"://") != std::string::npos) {
306 paths.push_back(line);
310 std::filesystem::path trackPath(line);
311 if (!trackPath.is_absolute())
312 trackPath = playlistDirectory / trackPath;
313 paths.push_back(trackPath.lexically_normal().string());
330 AVPacket *pkt = av_packet_alloc();
331 AVFrame *frame = av_frame_alloc();
332 if (!pkt || !frame) {
334 av_packet_free(&pkt);
336 av_frame_free(&frame);
340 while (av_read_frame(
fmtCtx, pkt) >= 0) {
342 int ret = avcodec_send_packet(
codecCtx, pkt);
344 ret = avcodec_receive_frame(
codecCtx, frame);
345 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
348 av_packet_unref(pkt);
349 av_frame_free(&frame);
350 av_packet_free(&pkt);
355 int outSamples = swr_get_out_samples(
swrCtx, frame->nb_samples);
356 std::vector<float> buf(outSamples);
357 uint8_t *outBuf =
reinterpret_cast<uint8_t *
>(buf.data());
358 int converted = swr_convert(
swrCtx, &outBuf, outSamples,
359 const_cast<const uint8_t **
>(frame->extended_data),
366 av_packet_unref(pkt);
370 int flushed = swr_convert(
swrCtx,
nullptr, 0,
nullptr, 0);
372 std::vector<float> buf(flushed);
373 uint8_t *outBuf =
reinterpret_cast<uint8_t *
>(buf.data());
374 flushed = swr_convert(
swrCtx, &outBuf, flushed,
nullptr, 0);
379 av_frame_free(&frame);
380 av_packet_free(&pkt);
387 if (avformat_open_input(&
fmtCtx, filepath.c_str(),
nullptr,
nullptr) < 0) {
388 std::cerr <<
"acmx2: file_audio: Cannot open: " << filepath <<
"\n";
391 if (avformat_find_stream_info(
fmtCtx,
nullptr) < 0) {
392 std::cerr <<
"acmx2: file_audio: Cannot find stream info\n";
398 for (
unsigned i = 0; i <
fmtCtx->nb_streams; ++i) {
399 if (
fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
405 std::cerr <<
"acmx2: file_audio: No audio stream found in: " << filepath <<
"\n";
412 std::cerr <<
"acmx2: file_audio: Unsupported audio codec\n";
416 codecCtx = avcodec_alloc_context3(codec);
418 if (avcodec_open2(
codecCtx, codec,
nullptr) < 0) {
419 std::cerr <<
"acmx2: file_audio: Cannot open codec\n";
425 AVChannelLayout outLayout = AV_CHANNEL_LAYOUT_MONO;
426 int ret = swr_alloc_set_opts2(&
swrCtx,
427 &outLayout, AV_SAMPLE_FMT_FLT, 44100,
430 if (ret < 0 || swr_init(
swrCtx) < 0) {
431 std::cerr <<
"acmx2: file_audio: Cannot init resampler\n";
437 std::cerr <<
"acmx2: file_audio: Decode failed\n";
444 const std::size_t decodedSampleCount =
decodedSamples.size() - initialSampleCount;
445 if (decodedSampleCount == 0) {
446 std::cerr <<
"acmx2: file_audio: No decoded samples in: " << filepath <<
"\n";
450 std::cout <<
"acmx2: file_audio: Loaded " << decodedSampleCount
451 <<
" samples (" << (decodedSampleCount / 44100.0)
452 <<
"s) from: " << filepath <<
"\n";
460 av_log_set_level(AV_LOG_ERROR);
463 const bool playlist =
isM3uPath(filepath);
464 const std::vector<std::string> requestedPaths =
466 : std::vector<std::string>{filepath};
467 if (requestedPaths.empty()) {
468 std::cerr <<
"acmx2: file_audio: M3U playlist contains no tracks: "
473 for (
const std::string &trackPath : requestedPaths) {
477 }
else if (playlist) {
478 std::cerr <<
"acmx2: file_audio: Skipping unusable playlist track: "
479 << trackPath <<
"\n";
492 std::cout <<
"acmx2: file_audio: Loaded M3U playlist with "
513 const std::size_t sourceIndex = std::min(
553 const bool output_playback =
568 const double samples_per_frame =
573 if (output_playback) {
574 next_playback_pos += std::max<size_t>(
575 1,
static_cast<size_t>(std::floor(samples_per_frame)));
578 next_playback_pos = std::max(
582 next_playback_pos = std::min(next_playback_pos,
decodedSamples.size());
584 unsigned int available =
585 static_cast<unsigned int>(next_playback_pos -
playbackPos);
590 if (output_playback) {
double file_audio_playback_time()
Return the current output-device playback timestamp in seconds.
static std::vector< float > decodedSamples
static SwrContext * swrCtx
static std::vector< std::size_t > fileAudioSourceEndPositions
bool file_audio_has_output_clock()
Check whether real-time output playback can provide the master clock.
static bool decodeAllSamples()
Decode every audio packet from the open format context.
static std::atomic< bool > fileAudioRepeat
static void closeDecoderResources()
static std::vector< std::string > readM3uPlaylist(const std::string &filepath)
static size_t playbackPos
void file_audio_close()
Stop file-audio playback and release decoder/sample resources.
static std::atomic< bool > fileAudioActive
std::vector< std::string > file_audio_source_paths()
Return the successfully decoded source tracks in playback order.
bool file_audio_open(const std::string &filepath)
Open and fully decode an audio file or M3U playlist to mono float PCM at 44.1 kHz.
void file_audio_set_repeat(bool enabled)
Enable or disable looping of the currently decoded audio file.
static double framePlaybackPos
bool file_audio_enable_output(int output_device)
Configure real-time playback of the decoded file through an output device.
static AVFormatContext * fmtCtx
static AVCodecContext * codecCtx
static std::vector< std::string > fileAudioSourcePaths
static std::string trimPlaylistLine(std::string line)
static int audioStreamIndex
static bool isM3uPath(const std::string &filepath)
bool file_audio_is_active()
Return true while decoded file-audio samples remain.
void file_audio_process_frame(double video_fps, acmx2::audio::AudioAnalyzer &analyzer)
Advance one video-frame worth of samples and update audio analysis.
std::string file_audio_current_source_path()
Return the source track at the current playback position.
static bool decodeAudioFile(const std::string &filepath)
File-based audio input for audio-reactive shaders.
Owns the audio-reactive analysis state shared by live and file audio.
void process_samples(const float *samples, unsigned int frame_count, unsigned int channels)
std::size_t position() const
bool is_configured() const
std::atomic< bool > started
std::size_t source_sample_count
unsigned int output_channels
static unsigned int choose_sample_rate(const std::vector< unsigned int > &rates)
static int audio_callback(void *output_buffer, void *, unsigned int frame_count, double, RtAudioStreamStatus, void *user_data)
std::atomic< bool > active
int write_samples(float *output, unsigned int frame_count)
double total_source_position
bool open(const float *samples, std::size_t sample_count, int output_device)
const float * source_samples
std::size_t total_position() const
FileAudioOutput()=default
std::atomic< std::size_t > total_playback_position
unsigned int output_sample_rate
std::atomic< std::size_t > playback_position
std::unique_ptr< FileAudioOutput > fileAudioOutput
constexpr unsigned int FILE_AUDIO_SAMPLE_RATE