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 = output_device >= 0 ?
static_cast<unsigned int>(output_device) :
audio.getDefaultOutputDevice();
74 const RtAudio::DeviceInfo info =
audio.getDeviceInfo(device);
75 if (info.outputChannels == 0) {
76 std::cerr <<
"acmx2: file_audio: Selected device has no output channels\n";
88 RtAudio::StreamParameters output_parameters;
89 output_parameters.deviceId = device;
91 output_parameters.firstChannel = 0;
93 unsigned int buffer_frames = 512;
96 std::cout <<
"acmx2: file_audio: Playback configured on device " << device <<
": " << info.name <<
" (" <<
output_channels <<
" ch, " <<
output_sample_rate <<
" Hz)\n";
98 }
catch (
const std::exception &error) {
99 std::cerr <<
"acmx2: file_audio: Could not open output stream: " << error.what() <<
"\n";
109 active.store(
true, std::memory_order_release);
110 started.store(
true, std::memory_order_release);
112 std::cout <<
"acmx2: file_audio: Playback started\n";
114 }
catch (
const std::exception &error) {
115 active.store(
false, std::memory_order_release);
116 started.store(
false, std::memory_order_release);
117 std::cerr <<
"acmx2: file_audio: Could not start output stream: " << error.what() <<
"\n";
123 active.store(
false, std::memory_order_release);
124 if (
audio.isStreamOpen()) {
126 if (
audio.isStreamRunning())
129 }
catch (
const std::exception &error) {
130 std::cerr <<
"acmx2: file_audio: Error closing output stream: " << error.what() <<
"\n";
134 started.store(
false, std::memory_order_release);
155 if (std::find(rates.begin(), rates.end(), 48000U) != rates.end())
157 return rates.front();
160 static int audio_callback(
void *output_buffer,
void *,
unsigned int frame_count,
double, RtAudioStreamStatus,
void *user_data) {
return static_cast<FileAudioOutput *
>(user_data)->
write_samples(
static_cast<float *
>(output_buffer), frame_count); }
163 if (output ==
nullptr)
167 for (
unsigned int frame = 0; frame < frame_count; ++frame) {
173 active.store(
false, std::memory_order_release);
181 const float fraction =
static_cast<float>(
source_position -
static_cast<double>(index));
186 active.store(
false, std::memory_order_release);
223 avformat_close_input(&
fmtCtx);
228 const std::string whitespace =
" \t\r\n";
229 const std::size_t first = line.find_first_not_of(whitespace);
230 if (first == std::string::npos)
232 const std::size_t last = line.find_last_not_of(whitespace);
233 return line.substr(first, last - first + 1);
237 std::string extension = std::filesystem::path(filepath).extension().string();
238 std::transform(extension.begin(), extension.end(), extension.begin(), [](
unsigned char value) { return static_cast<char>(std::tolower(value)); });
239 return extension ==
".m3u" || extension ==
".m3u8";
243 std::ifstream input(filepath);
245 std::cerr <<
"acmx2: file_audio: Cannot open M3U playlist: " << filepath <<
"\n";
249 const std::filesystem::path playlistDirectory = std::filesystem::absolute(std::filesystem::path(filepath)).parent_path();
250 std::vector<std::string> paths;
252 bool firstLine =
true;
253 while (std::getline(input, line)) {
254 if (firstLine && line.size() >= 3 &&
static_cast<unsigned char>(line[0]) == 0xef &&
static_cast<unsigned char>(line[1]) == 0xbb &&
static_cast<unsigned char>(line[2]) == 0xbf) {
259 if (line.empty() || line.front() ==
'#')
262 if (line.find(
"://") != std::string::npos) {
263 paths.push_back(line);
267 std::filesystem::path trackPath(line);
268 if (!trackPath.is_absolute())
269 trackPath = playlistDirectory / trackPath;
270 paths.push_back(trackPath.lexically_normal().string());
287 AVPacket *pkt = av_packet_alloc();
288 AVFrame *frame = av_frame_alloc();
289 if (!pkt || !frame) {
291 av_packet_free(&pkt);
293 av_frame_free(&frame);
297 while (av_read_frame(
fmtCtx, pkt) >= 0) {
299 int ret = avcodec_send_packet(
codecCtx, pkt);
301 ret = avcodec_receive_frame(
codecCtx, frame);
302 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
305 av_packet_unref(pkt);
306 av_frame_free(&frame);
307 av_packet_free(&pkt);
312 int outSamples = swr_get_out_samples(
swrCtx, frame->nb_samples);
313 std::vector<float> buf(outSamples);
314 uint8_t *outBuf =
reinterpret_cast<uint8_t *
>(buf.data());
315 int converted = swr_convert(
swrCtx, &outBuf, outSamples,
const_cast<const uint8_t **
>(frame->extended_data), frame->nb_samples);
321 av_packet_unref(pkt);
325 int flushed = swr_convert(
swrCtx,
nullptr, 0,
nullptr, 0);
327 std::vector<float> buf(flushed);
328 uint8_t *outBuf =
reinterpret_cast<uint8_t *
>(buf.data());
329 flushed = swr_convert(
swrCtx, &outBuf, flushed,
nullptr, 0);
334 av_frame_free(&frame);
335 av_packet_free(&pkt);
342 if (avformat_open_input(&
fmtCtx, filepath.c_str(),
nullptr,
nullptr) < 0) {
343 std::cerr <<
"acmx2: file_audio: Cannot open: " << filepath <<
"\n";
346 if (avformat_find_stream_info(
fmtCtx,
nullptr) < 0) {
347 std::cerr <<
"acmx2: file_audio: Cannot find stream info\n";
353 for (
unsigned i = 0; i <
fmtCtx->nb_streams; ++i) {
354 if (
fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
360 std::cerr <<
"acmx2: file_audio: No audio stream found in: " << filepath <<
"\n";
367 std::cerr <<
"acmx2: file_audio: Unsupported audio codec\n";
371 codecCtx = avcodec_alloc_context3(codec);
373 if (avcodec_open2(
codecCtx, codec,
nullptr) < 0) {
374 std::cerr <<
"acmx2: file_audio: Cannot open codec\n";
380 AVChannelLayout outLayout = AV_CHANNEL_LAYOUT_MONO;
381 int ret = swr_alloc_set_opts2(&
swrCtx, &outLayout, AV_SAMPLE_FMT_FLT, 44100, &
codecCtx->ch_layout,
codecCtx->sample_fmt,
codecCtx->sample_rate, 0,
nullptr);
382 if (ret < 0 || swr_init(
swrCtx) < 0) {
383 std::cerr <<
"acmx2: file_audio: Cannot init resampler\n";
389 std::cerr <<
"acmx2: file_audio: Decode failed\n";
396 const std::size_t decodedSampleCount =
decodedSamples.size() - initialSampleCount;
397 if (decodedSampleCount == 0) {
398 std::cerr <<
"acmx2: file_audio: No decoded samples in: " << filepath <<
"\n";
402 std::cout <<
"acmx2: file_audio: Loaded " << decodedSampleCount <<
" samples (" << (decodedSampleCount / 44100.0) <<
"s) from: " << filepath <<
"\n";
410 av_log_set_level(AV_LOG_ERROR);
413 const bool playlist =
isM3uPath(filepath);
414 const std::vector<std::string> requestedPaths = playlist ?
readM3uPlaylist(filepath) : std::vector<std::string>{filepath};
415 if (requestedPaths.empty()) {
416 std::cerr <<
"acmx2: file_audio: M3U playlist contains no tracks: " << filepath <<
"\n";
420 for (
const std::string &trackPath : requestedPaths) {
424 }
else if (playlist) {
425 std::cerr <<
"acmx2: file_audio: Skipping unusable playlist track: " << trackPath <<
"\n";
438 std::cout <<
"acmx2: file_audio: Loaded M3U playlist with " <<
fileAudioSourcePaths.size() <<
" track(s), " << (
decodedSamples.size() / 44100.0) <<
"s total: " << filepath <<
"\n";
496 const double samples_per_frame = video_fps > 0.0 ?
static_cast<double>(
FILE_AUDIO_SAMPLE_RATE) / video_fps : 512.0;
498 if (output_playback) {
499 next_playback_pos += std::max<size_t>(1,
static_cast<size_t>(std::floor(samples_per_frame)));
504 next_playback_pos = std::min(next_playback_pos,
decodedSamples.size());
506 unsigned int available =
static_cast<unsigned int>(next_playback_pos -
playbackPos);
511 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