ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
audio.cpp
Go to the documentation of this file.
1#include "audio.h"
2
4
5#include <cstring>
6
7namespace esphome::audio {
8
9// Euclidean's algorithm for finding the greatest common divisor
10static uint32_t gcd(uint32_t a, uint32_t b) {
11 while (b != 0) {
12 uint32_t t = b;
13 b = a % b;
14 a = t;
15 }
16 return a;
17}
18
19AudioStreamInfo::AudioStreamInfo(uint8_t bits_per_sample, uint8_t channels, uint32_t sample_rate)
20 : bits_per_sample_(bits_per_sample), channels_(channels), sample_rate_(sample_rate) {
21 this->ms_sample_rate_gcd_ = gcd(1000, this->sample_rate_);
22 this->bytes_per_sample_ = (this->bits_per_sample_ + 7) / 8;
23}
24
26 // In 64 bits: frames * 1000000 overflows 32 bits above 4294 frames, about 90 ms at 48 kHz.
27 return (static_cast<uint64_t>(frames) * 1000000 + (this->sample_rate_ >> 1)) / this->sample_rate_;
28}
29
31 uint32_t unprocessable_frames = *total_frames % (this->sample_rate_ / this->ms_sample_rate_gcd_);
32 uint32_t frames_for_ms_calculation = *total_frames - unprocessable_frames;
33
34 uint32_t playback_ms = (static_cast<uint64_t>(frames_for_ms_calculation) * 1000) / this->sample_rate_;
35 *total_frames = unprocessable_frames;
36 return playback_ms;
37}
38
40 return (this->bits_per_sample_ == rhs.get_bits_per_sample()) && (this->channels_ == rhs.get_channels()) &&
41 (this->sample_rate_ == rhs.get_sample_rate());
42}
43
45 switch (file_type) {
46#ifdef USE_AUDIO_FLAC_SUPPORT
48 return "FLAC";
49#endif
50#ifdef USE_AUDIO_MP3_SUPPORT
52 return "MP3";
53#endif
54#ifdef USE_AUDIO_OPUS_SUPPORT
56 return "OPUS";
57#endif
58#ifdef USE_AUDIO_WAV_SUPPORT
60 return "WAV";
61#endif
62 default:
63 return "unknown";
64 }
65}
66
67AudioFileType detect_audio_file_type(const char *content_type, const char *url) {
68 // Try Content-Type header first
69 if (content_type != nullptr && content_type[0] != '\0') {
70#ifdef USE_AUDIO_MP3_SUPPORT
71 if (strcasecmp(content_type, "mp3") == 0 || strcasecmp(content_type, "audio/mp3") == 0 ||
72 strcasecmp(content_type, "audio/mpeg") == 0) {
73 return AudioFileType::MP3;
74 }
75#endif
76#ifdef USE_AUDIO_WAV_SUPPORT
77 if (strcasecmp(content_type, "audio/wav") == 0) {
78 return AudioFileType::WAV;
79 }
80#endif
81#ifdef USE_AUDIO_FLAC_SUPPORT
82 if (strcasecmp(content_type, "audio/flac") == 0 || strcasecmp(content_type, "audio/x-flac") == 0) {
84 }
85#endif
86#ifdef USE_AUDIO_OPUS_SUPPORT
87 // Match "audio/ogg" with a codecs parameter containing "opus"
88 // Valid forms: audio/ogg;codecs=opus, audio/ogg; codecs="opus", etc.
89 // Plain "audio/ogg" without opus is not matched (almost always Ogg Vorbis)
90 if (strncasecmp(content_type, "audio/ogg", 9) == 0 && str_contains_ignore_case(content_type + 9, "opus")) {
92 }
93#endif
94 }
95
96 // Fallback to URL extension
97 if (url != nullptr && url[0] != '\0') {
98#ifdef USE_AUDIO_WAV_SUPPORT
99 if (str_endswith_ignore_case(url, ".wav")) {
100 return AudioFileType::WAV;
101 }
102#endif
103#ifdef USE_AUDIO_MP3_SUPPORT
104 if (str_endswith_ignore_case(url, ".mp3")) {
105 return AudioFileType::MP3;
106 }
107#endif
108#ifdef USE_AUDIO_FLAC_SUPPORT
109 if (str_endswith_ignore_case(url, ".flac")) {
110 return AudioFileType::FLAC;
111 }
112#endif
113#ifdef USE_AUDIO_OPUS_SUPPORT
114 if (str_endswith_ignore_case(url, ".opus")) {
115 return AudioFileType::OPUS;
116 }
117#endif
118 }
119
120 return AudioFileType::NONE;
121}
122
123void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
124 size_t samples_to_scale) {
125 // Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same.
126 for (size_t i = 0; i < samples_to_scale; i++) {
127 int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor;
128 output_buffer[i] = (int16_t) (acc >> 15);
129 }
130}
131
132} // namespace esphome::audio
uint8_t get_bits_per_sample() const
Definition audio.h:28
uint32_t frames_to_microseconds(uint32_t frames) const
Computes the duration, in microseconds, the given amount of frames represents.
Definition audio.cpp:25
uint8_t get_channels() const
Definition audio.h:29
uint32_t frames_to_milliseconds_with_remainder(uint32_t *frames) const
Computes the duration, in milliseconds, the given amount of frames represents.
Definition audio.cpp:30
bool operator==(const AudioStreamInfo &rhs) const
Definition audio.cpp:39
uint32_t get_sample_rate() const
Definition audio.h:30
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor, size_t samples_to_scale)
Definition audio.cpp:123
int16_t int16_t scale_factor
Definition audio.h:151
const char * audio_file_type_to_string(AudioFileType file_type)
Helper function to convert file type to a const char string.
Definition audio.cpp:44
int16_t * output_buffer
Definition audio.h:151
AudioFileType detect_audio_file_type(const char *content_type, const char *url)
Detect audio file type from a Content-Type header value and/or URL extension.
Definition audio.cpp:67
int16_t int16_t size_t samples_to_scale
Definition audio.h:152
bool str_contains_ignore_case(const char *haystack, const char *needle)
Case-insensitive check if needle string is contained in haystack (no heap allocation).
Definition helpers.h:1166
bool str_endswith_ignore_case(const char *str, size_t str_len, const char *suffix, size_t suffix_len)
Case-insensitive check if string ends with suffix (no heap allocation).
Definition helpers.cpp:226
static void uint32_t