Do our own resampling
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
idf_component_register(
|
||||
SRCS "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp"
|
||||
"stream_message.cpp" "i2s_audio_output.cpp" "stream_buffer.cpp" "track_queue.cpp"
|
||||
"stream_event.cpp" "stream_info.cpp" "audio_fsm.cpp" "sink_mixer.cpp"
|
||||
"stream_event.cpp" "stream_info.cpp" "audio_fsm.cpp" "sink_mixer.cpp" "resample.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm" "database" "system_fsm" "playlist" "libsamplerate")
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "pipeline.hpp"
|
||||
#include "sample.hpp"
|
||||
#include "sink_mixer.hpp"
|
||||
#include "span.hpp"
|
||||
|
||||
@@ -225,7 +226,7 @@ auto AudioTask::BeginDecoding(InputStream& stream) -> bool {
|
||||
codecs::ICodec::OutputFormat format = res.second.value();
|
||||
StreamInfo::Pcm new_format{
|
||||
.channels = format.num_channels,
|
||||
.bits_per_sample = format.bits_per_sample,
|
||||
.bits_per_sample = 32,
|
||||
.sample_rate = format.sample_rate_hz,
|
||||
};
|
||||
|
||||
@@ -255,7 +256,8 @@ auto AudioTask::ContinueDecoding(InputStream& stream) -> bool {
|
||||
while (!stream.data().empty()) {
|
||||
OutputStream writer{codec_buffer_.get()};
|
||||
|
||||
auto res = codec_->ContinueStream(stream.data(), writer.data());
|
||||
auto res =
|
||||
codec_->ContinueStream(stream.data(), writer.data_as<sample::Sample>());
|
||||
|
||||
stream.consume(res.first);
|
||||
|
||||
@@ -266,7 +268,7 @@ auto AudioTask::ContinueDecoding(InputStream& stream) -> bool {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
writer.add(res.second->bytes_written);
|
||||
writer.add(res.second->samples_written * sizeof(sample::Sample));
|
||||
|
||||
InputStream reader{codec_buffer_.get()};
|
||||
SendToSink(reader);
|
||||
@@ -295,12 +297,12 @@ auto AudioTask::FinishDecoding(InputStream& stream) -> void {
|
||||
InputStream padded_stream{mad_buffer.get()};
|
||||
|
||||
OutputStream writer{codec_buffer_.get()};
|
||||
auto res = codec_->ContinueStream(stream.data(), writer.data());
|
||||
auto res = codec_->ContinueStream(stream.data(), writer.data_as<sample::Sample>());
|
||||
if (res.second.has_error()) {
|
||||
return;
|
||||
}
|
||||
|
||||
writer.add(res.second->bytes_written);
|
||||
writer.add(res.second->samples_written * sizeof(sample::Sample));
|
||||
|
||||
InputStream reader{codec_buffer_.get()};
|
||||
SendToSink(reader);
|
||||
|
||||
@@ -117,11 +117,18 @@ auto I2SAudioOutput::AdjustVolumeDown() -> bool {
|
||||
|
||||
auto I2SAudioOutput::PrepareFormat(const StreamInfo::Pcm& orig)
|
||||
-> StreamInfo::Pcm {
|
||||
/*
|
||||
return StreamInfo::Pcm{
|
||||
.channels = std::min<uint8_t>(orig.channels, 2),
|
||||
.bits_per_sample = std::clamp<uint8_t>(orig.bits_per_sample, 16, 32),
|
||||
.sample_rate = std::clamp<uint32_t>(orig.sample_rate, 8000, 96000),
|
||||
};
|
||||
*/
|
||||
return StreamInfo::Pcm{
|
||||
.channels = std::min<uint8_t>(orig.channels, 2),
|
||||
.bits_per_sample = 16,
|
||||
.sample_rate = 48000,
|
||||
};
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::Configure(const StreamInfo::Pcm& pcm) -> void {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* FIR filter coefficients from resample-1.x smallfilter.h
|
||||
* see Digital Audio Resampling Home Page located at
|
||||
* http://ccrma.stanford.edu/~jos/resample/
|
||||
*/
|
||||
32767, 32766, 32764, 32760, 32755, 32749, 32741, 32731, 32721, 32708,
|
||||
32695, 32679, 32663, 32645, 32625, 32604, 32582, 32558, 32533, 32506,
|
||||
32478, 32448, 32417, 32385, 32351, 32316, 32279, 32241, 32202, 32161,
|
||||
32119, 32075, 32030, 31984, 31936, 31887, 31836, 31784, 31731, 31676,
|
||||
31620, 31563, 31504, 31444, 31383, 31320, 31256, 31191, 31124, 31056,
|
||||
30987, 30916, 30845, 30771, 30697, 30621, 30544, 30466, 30387, 30306,
|
||||
30224, 30141, 30057, 29971, 29884, 29796, 29707, 29617, 29525, 29433,
|
||||
29339, 29244, 29148, 29050, 28952, 28852, 28752, 28650, 28547, 28443,
|
||||
28338, 28232, 28125, 28017, 27908, 27797, 27686, 27574, 27461, 27346,
|
||||
27231, 27115, 26998, 26879, 26760, 26640, 26519, 26398, 26275, 26151,
|
||||
26027, 25901, 25775, 25648, 25520, 25391, 25262, 25131, 25000, 24868,
|
||||
24735, 24602, 24467, 24332, 24197, 24060, 23923, 23785, 23647, 23507,
|
||||
23368, 23227, 23086, 22944, 22802, 22659, 22515, 22371, 22226, 22081,
|
||||
21935, 21789, 21642, 21494, 21346, 21198, 21049, 20900, 20750, 20600,
|
||||
20449, 20298, 20146, 19995, 19842, 19690, 19537, 19383, 19230, 19076,
|
||||
18922, 18767, 18612, 18457, 18302, 18146, 17990, 17834, 17678, 17521,
|
||||
17365, 17208, 17051, 16894, 16737, 16579, 16422, 16264, 16106, 15949,
|
||||
15791, 15633, 15475, 15317, 15159, 15001, 14843, 14685, 14527, 14369,
|
||||
14212, 14054, 13896, 13739, 13581, 13424, 13266, 13109, 12952, 12795,
|
||||
12639, 12482, 12326, 12170, 12014, 11858, 11703, 11548, 11393, 11238,
|
||||
11084, 10929, 10776, 10622, 10469, 10316, 10164, 10011, 9860, 9708,
|
||||
9557, 9407, 9256, 9106, 8957, 8808, 8659, 8511, 8364, 8216, 8070,
|
||||
7924, 7778, 7633, 7488, 7344, 7200, 7057, 6914, 6773, 6631, 6490,
|
||||
6350, 6210, 6071, 5933, 5795, 5658, 5521, 5385, 5250, 5115, 4981,
|
||||
4848, 4716, 4584, 4452, 4322, 4192, 4063, 3935, 3807, 3680, 3554,
|
||||
3429, 3304, 3180, 3057, 2935, 2813, 2692, 2572, 2453, 2335, 2217,
|
||||
2101, 1985, 1870, 1755, 1642, 1529, 1418, 1307, 1197, 1088, 979, 872,
|
||||
765, 660, 555, 451, 348, 246, 145, 44, -54, -153, -250, -347, -443,
|
||||
-537, -631, -724, -816, -908, -998, -1087, -1175, -1263, -1349, -1435,
|
||||
-1519, -1603, -1685, -1767, -1848, -1928, -2006, -2084, -2161, -2237,
|
||||
-2312, -2386, -2459, -2531, -2603, -2673, -2742, -2810, -2878, -2944,
|
||||
-3009, -3074, -3137, -3200, -3261, -3322, -3381, -3440, -3498, -3554,
|
||||
-3610, -3665, -3719, -3772, -3824, -3875, -3925, -3974, -4022, -4069,
|
||||
-4116, -4161, -4205, -4249, -4291, -4333, -4374, -4413, -4452, -4490,
|
||||
-4527, -4563, -4599, -4633, -4666, -4699, -4730, -4761, -4791, -4820,
|
||||
-4848, -4875, -4901, -4926, -4951, -4974, -4997, -5019, -5040, -5060,
|
||||
-5080, -5098, -5116, -5133, -5149, -5164, -5178, -5192, -5205, -5217,
|
||||
-5228, -5238, -5248, -5257, -5265, -5272, -5278, -5284, -5289, -5293,
|
||||
-5297, -5299, -5301, -5303, -5303, -5303, -5302, -5300, -5298, -5295,
|
||||
-5291, -5287, -5282, -5276, -5270, -5263, -5255, -5246, -5237, -5228,
|
||||
-5217, -5206, -5195, -5183, -5170, -5157, -5143, -5128, -5113, -5097,
|
||||
-5081, -5064, -5047, -5029, -5010, -4991, -4972, -4952, -4931, -4910,
|
||||
-4889, -4867, -4844, -4821, -4797, -4774, -4749, -4724, -4699, -4673,
|
||||
-4647, -4620, -4593, -4566, -4538, -4510, -4481, -4452, -4422, -4393,
|
||||
-4363, -4332, -4301, -4270, -4238, -4206, -4174, -4142, -4109, -4076,
|
||||
-4042, -4009, -3975, -3940, -3906, -3871, -3836, -3801, -3765, -3729,
|
||||
-3693, -3657, -3620, -3584, -3547, -3510, -3472, -3435, -3397, -3360,
|
||||
-3322, -3283, -3245, -3207, -3168, -3129, -3091, -3052, -3013, -2973,
|
||||
-2934, -2895, -2855, -2816, -2776, -2736, -2697, -2657, -2617, -2577,
|
||||
-2537, -2497, -2457, -2417, -2377, -2337, -2297, -2256, -2216, -2176,
|
||||
-2136, -2096, -2056, -2016, -1976, -1936, -1896, -1856, -1817, -1777,
|
||||
-1737, -1698, -1658, -1619, -1579, -1540, -1501, -1462, -1423, -1384,
|
||||
-1345, -1306, -1268, -1230, -1191, -1153, -1115, -1077, -1040, -1002,
|
||||
-965, -927, -890, -854, -817, -780, -744, -708, -672, -636, -600,
|
||||
-565, -530, -494, -460, -425, -391, -356, -322, -289, -255, -222,
|
||||
-189, -156, -123, -91, -59, -27, 4, 35, 66, 97, 127, 158, 188, 218,
|
||||
247, 277, 306, 334, 363, 391, 419, 447, 474, 501, 528, 554, 581, 606,
|
||||
632, 657, 683, 707, 732, 756, 780, 803, 827, 850, 872, 895, 917, 939,
|
||||
960, 981, 1002, 1023, 1043, 1063, 1082, 1102, 1121, 1139, 1158, 1176,
|
||||
1194, 1211, 1228, 1245, 1262, 1278, 1294, 1309, 1325, 1340, 1354,
|
||||
1369, 1383, 1397, 1410, 1423, 1436, 1448, 1461, 1473, 1484, 1496,
|
||||
1507, 1517, 1528, 1538, 1548, 1557, 1566, 1575, 1584, 1592, 1600,
|
||||
1608, 1616, 1623, 1630, 1636, 1643, 1649, 1654, 1660, 1665, 1670,
|
||||
1675, 1679, 1683, 1687, 1690, 1694, 1697, 1700, 1702, 1704, 1706,
|
||||
1708, 1709, 1711, 1712, 1712, 1713, 1713, 1713, 1713, 1712, 1711,
|
||||
1710, 1709, 1708, 1706, 1704, 1702, 1700, 1697, 1694, 1691, 1688,
|
||||
1685, 1681, 1677, 1673, 1669, 1664, 1660, 1655, 1650, 1644, 1639,
|
||||
1633, 1627, 1621, 1615, 1609, 1602, 1596, 1589, 1582, 1575, 1567,
|
||||
1560, 1552, 1544, 1536, 1528, 1520, 1511, 1503, 1494, 1485, 1476,
|
||||
1467, 1458, 1448, 1439, 1429, 1419, 1409, 1399, 1389, 1379, 1368,
|
||||
1358, 1347, 1337, 1326, 1315, 1304, 1293, 1282, 1271, 1260, 1248,
|
||||
1237, 1225, 1213, 1202, 1190, 1178, 1166, 1154, 1142, 1130, 1118,
|
||||
1106, 1094, 1081, 1069, 1057, 1044, 1032, 1019, 1007, 994, 981, 969,
|
||||
956, 943, 931, 918, 905, 892, 879, 867, 854, 841, 828, 815, 802, 790,
|
||||
777, 764, 751, 738, 725, 713, 700, 687, 674, 662, 649, 636, 623, 611,
|
||||
598, 585, 573, 560, 548, 535, 523, 510, 498, 486, 473, 461, 449, 437,
|
||||
425, 413, 401, 389, 377, 365, 353, 341, 330, 318, 307, 295, 284, 272,
|
||||
261, 250, 239, 228, 217, 206, 195, 184, 173, 163, 152, 141, 131, 121,
|
||||
110, 100, 90, 80, 70, 60, 51, 41, 31, 22, 12, 3, -5, -14, -23, -32,
|
||||
-41, -50, -59, -67, -76, -84, -93, -101, -109, -117, -125, -133, -140,
|
||||
-148, -156, -163, -170, -178, -185, -192, -199, -206, -212, -219,
|
||||
-226, -232, -239, -245, -251, -257, -263, -269, -275, -280, -286,
|
||||
-291, -297, -302, -307, -312, -317, -322, -327, -332, -336, -341,
|
||||
-345, -349, -354, -358, -362, -366, -369, -373, -377, -380, -384,
|
||||
-387, -390, -394, -397, -400, -402, -405, -408, -411, -413, -416,
|
||||
-418, -420, -422, -424, -426, -428, -430, -432, -433, -435, -436,
|
||||
-438, -439, -440, -442, -443, -444, -445, -445, -446, -447, -447,
|
||||
-448, -448, -449, -449, -449, -449, -449, -449, -449, -449, -449,
|
||||
-449, -449, -448, -448, -447, -447, -446, -445, -444, -443, -443,
|
||||
-442, -441, -440, -438, -437, -436, -435, -433, -432, -430, -429,
|
||||
-427, -426, -424, -422, -420, -419, -417, -415, -413, -411, -409,
|
||||
-407, -405, -403, -400, -398, -396, -393, -391, -389, -386, -384,
|
||||
-381, -379, -376, -374, -371, -368, -366, -363, -360, -357, -355,
|
||||
-352, -349, -346, -343, -340, -337, -334, -331, -328, -325, -322,
|
||||
-319, -316, -313, -310, -307, -304, -301, -298, -294, -291, -288,
|
||||
-285, -282, -278, -275, -272, -269, -265, -262, -259, -256, -252,
|
||||
-249, -246, -243, -239, -236, -233, -230, -226, -223, -220, -217,
|
||||
-213, -210, -207, -204, -200, -197, -194, -191, -187, -184, -181,
|
||||
-178, -175, -172, -168, -165, -162, -159, -156, -153, -150, -147,
|
||||
-143, -140, -137, -134, -131, -128, -125, -122, -120, -117, -114,
|
||||
-111, -108, -105, -102, -99, -97, -94, -91, -88, -86, -83, -80, -78,
|
||||
-75, -72, -70, -67, -65, -62, -59, -57, -55, -52, -50, -47, -45, -43,
|
||||
-40, -38, -36, -33, -31, -29, -27, -25, -22, -20, -18, -16, -14, -12,
|
||||
-10, -8, -6, -4, -2, 0, 0, 2, 4, 6, 8, 9, 11, 13, 14, 16, 17, 19, 21,
|
||||
22, 24, 25, 27, 28, 29, 31, 32, 33, 35, 36, 37, 38, 40, 41, 42, 43,
|
||||
44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59,
|
||||
59, 60, 61, 62, 62, 63, 63, 64, 64, 65, 66, 66, 66, 67, 67, 68, 68,
|
||||
69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72,
|
||||
72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72,
|
||||
72, 71, 71, 71, 71, 71, 70, 70, 70, 70, 69, 69, 69, 69, 68, 68, 68,
|
||||
67, 67, 67, 66, 66, 66, 65, 65, 64, 64, 64, 63, 63, 62, 62, 62, 61,
|
||||
61, 60, 60, 59, 59, 58, 58, 58, 57, 57, 56, 56, 55, 55, 54, 54, 53,
|
||||
53, 52, 52, 51, 51, 50, 50, 49, 48, 48, 47, 47, 46, 46, 45, 45, 44,
|
||||
44, 43, 43, 42, 42, 41, 41, 40, 39, 39, 38, 38, 37, 37, 36, 36, 35,
|
||||
35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 28, 28, 27, 27,
|
||||
26, 26, 25, 25, 24, 24, 23, 23, 23, 22, 22, 21, 21, 20, 20, 20, 19,
|
||||
19, 18, 18, 17, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 12,
|
||||
12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6,
|
||||
6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1,
|
||||
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/_stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include "span.hpp"
|
||||
|
||||
#include "sample.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
class Channel;
|
||||
|
||||
class Resampler {
|
||||
public:
|
||||
Resampler(uint32_t source_sample_rate,
|
||||
uint32_t target_sample_rate,
|
||||
uint8_t num_channels);
|
||||
|
||||
~Resampler();
|
||||
|
||||
auto source_sample_rate() -> uint32_t { return source_sample_rate_; }
|
||||
auto target_sample_rate() -> uint32_t { return target_sample_rate_; }
|
||||
auto channels() -> uint_fast8_t { return num_channels_; }
|
||||
|
||||
auto Process(cpp::span<const sample::Sample> input,
|
||||
cpp::span<sample::Sample> output,
|
||||
bool end_of_data) -> std::pair<size_t,size_t>;
|
||||
|
||||
private:
|
||||
auto ApplyDither(cpp::span<sample::Sample>) -> void;
|
||||
|
||||
uint32_t source_sample_rate_;
|
||||
uint32_t target_sample_rate_;
|
||||
uint32_t factor_;
|
||||
|
||||
uint8_t num_channels_;
|
||||
std::vector<Channel> channels_;
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "resample.hpp"
|
||||
#include "sample.hpp"
|
||||
#include "samplerate.h"
|
||||
|
||||
#include "audio_decoder.hpp"
|
||||
@@ -38,12 +40,10 @@ class SinkMixer {
|
||||
auto SetTargetFormat(const StreamInfo::Pcm& format) -> void;
|
||||
auto HandleBytes() -> void;
|
||||
|
||||
template <typename T>
|
||||
auto ConvertFixedToFloating(InputStream&, OutputStream&) -> void;
|
||||
auto Resample(float, int, InputStream&, OutputStream&) -> void;
|
||||
template <typename T>
|
||||
auto Quantise(InputStream&) -> std::size_t;
|
||||
|
||||
auto Resample(InputStream&, OutputStream&) -> bool;
|
||||
auto ApplyDither(cpp::span<sample::Sample> samples, uint_fast8_t bits) -> void;
|
||||
auto Downscale(cpp::span<sample::Sample>, cpp::span<int16_t>) -> void;
|
||||
|
||||
enum class Command {
|
||||
kReadBytes,
|
||||
kSetSourceFormat,
|
||||
@@ -58,31 +58,14 @@ class SinkMixer {
|
||||
QueueHandle_t commands_;
|
||||
SemaphoreHandle_t is_idle_;
|
||||
|
||||
SRC_STATE* resampler_;
|
||||
std::unique_ptr<Resampler> resampler_;
|
||||
|
||||
std::unique_ptr<RawStream> input_stream_;
|
||||
std::unique_ptr<RawStream> floating_point_stream_;
|
||||
std::unique_ptr<RawStream> resampled_stream_;
|
||||
|
||||
cpp::span<std::byte> quantisation_buffer_;
|
||||
cpp::span<short> quantisation_buffer_as_shorts_;
|
||||
cpp::span<int> quantisation_buffer_as_ints_;
|
||||
|
||||
StreamInfo::Pcm target_format_;
|
||||
StreamBufferHandle_t source_;
|
||||
StreamBufferHandle_t sink_;
|
||||
};
|
||||
|
||||
template <>
|
||||
auto SinkMixer::ConvertFixedToFloating<short>(InputStream&, OutputStream&)
|
||||
-> void;
|
||||
template <>
|
||||
auto SinkMixer::ConvertFixedToFloating<int>(InputStream&, OutputStream&)
|
||||
-> void;
|
||||
|
||||
template <>
|
||||
auto SinkMixer::Quantise<short>(InputStream&) -> std::size_t;
|
||||
template <>
|
||||
auto SinkMixer::Quantise<int>(InputStream&) -> std::size_t;
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -77,7 +77,7 @@ class StreamInfo {
|
||||
// The sample rate.
|
||||
uint32_t sample_rate;
|
||||
|
||||
auto real_bytes_per_sample() const -> uint8_t {
|
||||
auto bytes_per_sample() const -> uint8_t {
|
||||
return bits_per_sample == 16 ? 2 : 4;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
#include "resample.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "sample.hpp"
|
||||
#include "stream_info.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
static constexpr size_t kFilterSize = 1536;
|
||||
|
||||
constexpr auto calc_deltas(const std::array<int32_t, kFilterSize>& filter)
|
||||
-> std::array<int32_t, kFilterSize> {
|
||||
std::array<int32_t, kFilterSize> deltas;
|
||||
for (size_t n = 0; n < kFilterSize - 1; n++)
|
||||
deltas[n] = filter[n + 1] - filter[n];
|
||||
return deltas;
|
||||
}
|
||||
|
||||
static const std::array<int32_t, kFilterSize> kFilter{
|
||||
#include "fir.h"
|
||||
};
|
||||
|
||||
static const std::array<int32_t, kFilterSize> kFilterDeltas =
|
||||
calc_deltas(kFilter);
|
||||
|
||||
class Channel {
|
||||
public:
|
||||
Channel(uint32_t src_rate,
|
||||
uint32_t dest_rate,
|
||||
size_t chunk_size,
|
||||
size_t skip);
|
||||
~Channel();
|
||||
|
||||
auto output_chunk_size() -> size_t { return output_chunk_size_; }
|
||||
|
||||
auto FlushSamples(cpp::span<sample::Sample> out) -> size_t;
|
||||
auto AddSample(sample::Sample, cpp::span<sample::Sample> out) -> std::size_t;
|
||||
auto ApplyFilter() -> sample::Sample;
|
||||
|
||||
private:
|
||||
size_t output_chunk_size_;
|
||||
size_t skip_;
|
||||
|
||||
uint32_t factor_; /* factor */
|
||||
|
||||
uint32_t time_; /* time */
|
||||
|
||||
uint32_t time_per_filter_iteration_; /* output step */
|
||||
uint32_t filter_step_; /* filter step */
|
||||
uint32_t filter_end_; /* filter end */
|
||||
|
||||
int32_t unity_scale_; /* unity scale */
|
||||
|
||||
int32_t samples_per_filter_wing_; /* extra samples */
|
||||
int32_t latest_sample_; /* buffer index */
|
||||
cpp::span<int32_t> sample_buffer_; /* the buffer */
|
||||
};
|
||||
|
||||
enum {
|
||||
Nl = 8, /* 2^Nl samples per zero crossing in fir */
|
||||
Nη = 8, /* phase bits for filter interpolation */
|
||||
kPhaseBits = Nl + Nη, /* phase bits (fract of fixed point) */
|
||||
One = 1 << kPhaseBits,
|
||||
};
|
||||
|
||||
Channel::Channel(uint32_t irate, uint32_t orate, size_t count, size_t skip)
|
||||
: skip_(skip) {
|
||||
factor_ = ((uint64_t)orate << kPhaseBits) / irate;
|
||||
if (factor_ != One) {
|
||||
time_per_filter_iteration_ = ((uint64_t)irate << kPhaseBits) / orate;
|
||||
filter_step_ = 1 << (Nl + Nη);
|
||||
filter_end_ = kFilterSize << Nη;
|
||||
samples_per_filter_wing_ = 1 + (filter_end_ / filter_step_);
|
||||
unity_scale_ = 13128; /* unity scale factor for fir */
|
||||
if (factor_ < One) {
|
||||
unity_scale_ *= factor_;
|
||||
unity_scale_ >>= kPhaseBits;
|
||||
filter_step_ *= factor_;
|
||||
filter_step_ >>= kPhaseBits;
|
||||
samples_per_filter_wing_ *= time_per_filter_iteration_;
|
||||
samples_per_filter_wing_ >>= kPhaseBits;
|
||||
}
|
||||
latest_sample_ = samples_per_filter_wing_;
|
||||
time_ = latest_sample_ << kPhaseBits;
|
||||
|
||||
size_t buf_size = samples_per_filter_wing_ * 2 + count;
|
||||
int32_t* buf = new int32_t[buf_size];
|
||||
sample_buffer_ = {buf, buf_size};
|
||||
count += buf_size; /* account for buffer accumulation */
|
||||
}
|
||||
output_chunk_size_ = ((uint64_t)count * factor_) >> kPhaseBits;
|
||||
}
|
||||
|
||||
Channel::~Channel() {
|
||||
delete sample_buffer_.data();
|
||||
}
|
||||
|
||||
auto Channel::ApplyFilter() -> sample::Sample {
|
||||
uint32_t iteration, p, i;
|
||||
int32_t *sample, a;
|
||||
|
||||
int64_t value = 0;
|
||||
|
||||
// I did my best, but I'll be honest with you I've no idea about any of this
|
||||
// maths stuff.
|
||||
|
||||
// Left wing of the filter.
|
||||
sample = &sample_buffer_[time_ >> kPhaseBits];
|
||||
p = time_ & ((1 << kPhaseBits) - 1);
|
||||
iteration = factor_ < One ? (factor_ * p) >> kPhaseBits : p;
|
||||
while (iteration < filter_end_) {
|
||||
i = iteration >> Nη;
|
||||
a = iteration & ((1 << Nη) - 1);
|
||||
iteration += filter_step_;
|
||||
a *= kFilterDeltas[i];
|
||||
a >>= Nη;
|
||||
a += kFilter[i];
|
||||
value += static_cast<int64_t>(*--sample) * a;
|
||||
}
|
||||
|
||||
// Right wing of the filter.
|
||||
sample = &sample_buffer_[time_ >> kPhaseBits];
|
||||
p = (One - p) & ((1 << kPhaseBits) - 1);
|
||||
iteration = factor_ < One ? (factor_ * p) >> kPhaseBits : p;
|
||||
if (p == 0) /* skip h[0] as it was already been summed above if p == 0 */
|
||||
iteration += filter_step_;
|
||||
while (iteration < filter_end_) {
|
||||
i = iteration >> Nη;
|
||||
a = iteration & ((1 << Nη) - 1);
|
||||
iteration += filter_step_;
|
||||
a *= kFilterDeltas[i];
|
||||
a >>= Nη;
|
||||
a += kFilter[i];
|
||||
value += static_cast<int64_t>(*sample++) * a;
|
||||
}
|
||||
|
||||
/* scale */
|
||||
value >>= 2;
|
||||
value *= unity_scale_;
|
||||
value >>= 27;
|
||||
|
||||
return sample::Clip(value);
|
||||
}
|
||||
|
||||
auto Channel::FlushSamples(cpp::span<sample::Sample> out) -> size_t {
|
||||
size_t zeroes_needed = (2 * samples_per_filter_wing_) - latest_sample_;
|
||||
size_t produced = 0;
|
||||
while (zeroes_needed > 0) {
|
||||
produced += AddSample(0, out.subspan(produced));
|
||||
zeroes_needed--;
|
||||
}
|
||||
return produced;
|
||||
}
|
||||
|
||||
auto Channel::AddSample(sample::Sample in, cpp::span<sample::Sample> out)
|
||||
-> size_t {
|
||||
// Add the latest sample to our working buffer.
|
||||
sample_buffer_[latest_sample_++] = in;
|
||||
|
||||
// If we don't have enough samples to run the filter, then bail out and wait
|
||||
// for more.
|
||||
if (latest_sample_ < 2 * samples_per_filter_wing_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply the filter to the buffered samples. First, we work out how long (in
|
||||
// samples) we can run the filter for before running out. This isn't as
|
||||
// trivial as it might look; e.g. depending on the resampling factor we might
|
||||
// be doubling the number of samples, or halving them.
|
||||
uint32_t max_time = (latest_sample_ - samples_per_filter_wing_) << kPhaseBits;
|
||||
size_t samples_output = 0;
|
||||
while (time_ < max_time) {
|
||||
out[skip_ * samples_output++] = ApplyFilter();
|
||||
time_ += time_per_filter_iteration_;
|
||||
}
|
||||
|
||||
// If we are approaching the end of our buffer, we need to shift all the data
|
||||
// in it down to the front to make room for more samples.
|
||||
int32_t current_sample = time_ >> kPhaseBits;
|
||||
if (current_sample >= (sample_buffer_.size() - samples_per_filter_wing_)) {
|
||||
// NB: bit shifting back and forth means we're only modifying `time` by
|
||||
// whole samples.
|
||||
time_ -= current_sample << kPhaseBits;
|
||||
time_ += samples_per_filter_wing_ << kPhaseBits;
|
||||
|
||||
int32_t new_current_sample = time_ >> kPhaseBits;
|
||||
new_current_sample -= samples_per_filter_wing_;
|
||||
current_sample -= samples_per_filter_wing_;
|
||||
|
||||
int32_t samples_to_move = latest_sample_ - current_sample;
|
||||
if (samples_to_move > 0) {
|
||||
auto samples = sample_buffer_.subspan(current_sample, samples_to_move);
|
||||
std::copy_backward(samples.begin(), samples.end(),
|
||||
sample_buffer_.first(new_current_sample).end());
|
||||
latest_sample_ = new_current_sample + samples_to_move;
|
||||
} else {
|
||||
latest_sample_ = new_current_sample;
|
||||
}
|
||||
}
|
||||
|
||||
return samples_output;
|
||||
}
|
||||
|
||||
static const size_t kChunkSizeSamples = 256;
|
||||
|
||||
Resampler::Resampler(uint32_t source_sample_rate,
|
||||
uint32_t target_sample_rate,
|
||||
uint8_t num_channels)
|
||||
: source_sample_rate_(source_sample_rate),
|
||||
target_sample_rate_(target_sample_rate),
|
||||
factor_(((uint64_t)target_sample_rate << kPhaseBits) /
|
||||
source_sample_rate),
|
||||
num_channels_(num_channels),
|
||||
channels_() {
|
||||
for (int i = 0; i < num_channels; i++) {
|
||||
channels_.emplace_back(source_sample_rate, target_sample_rate,
|
||||
kChunkSizeSamples, num_channels);
|
||||
}
|
||||
}
|
||||
|
||||
Resampler::~Resampler() {}
|
||||
|
||||
auto Resampler::Process(cpp::span<const sample::Sample> input,
|
||||
cpp::span<sample::Sample> output,
|
||||
bool end_of_data) -> std::pair<size_t, size_t> {
|
||||
size_t samples_used = 0;
|
||||
std::vector<size_t> samples_produced = {num_channels_, 0};
|
||||
size_t total_samples_produced = 0;
|
||||
|
||||
size_t slop = (factor_ >> kPhaseBits) + 1;
|
||||
|
||||
uint_fast8_t cur_channel = 0;
|
||||
|
||||
while (input.size() > samples_used &&
|
||||
output.size() > total_samples_produced + slop) {
|
||||
// Work out where the next set of samples should be placed.
|
||||
size_t next_output_index =
|
||||
(samples_produced[cur_channel] * num_channels_) + cur_channel;
|
||||
|
||||
// Generate the next samples
|
||||
size_t new_samples = channels_[cur_channel].AddSample(
|
||||
input[samples_used++], output.subspan(next_output_index));
|
||||
|
||||
samples_produced[cur_channel] += new_samples;
|
||||
total_samples_produced += new_samples;
|
||||
|
||||
cur_channel = (cur_channel + 1) % num_channels_;
|
||||
}
|
||||
|
||||
return {samples_used, total_samples_produced};
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
+69
-152
@@ -13,6 +13,8 @@
|
||||
#include "esp_log.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "freertos/projdefs.h"
|
||||
#include "resample.hpp"
|
||||
#include "sample.hpp"
|
||||
#include "samplerate.h"
|
||||
|
||||
#include "stream_info.hpp"
|
||||
@@ -21,10 +23,7 @@
|
||||
static constexpr char kTag[] = "mixer";
|
||||
|
||||
static constexpr std::size_t kSourceBufferLength = 2 * 1024;
|
||||
static constexpr std::size_t kInputBufferLength = 2 * 1024;
|
||||
static constexpr std::size_t kReformatBufferLength = 8 * 1024;
|
||||
static constexpr std::size_t kResampleBufferLength = kReformatBufferLength;
|
||||
static constexpr std::size_t kQuantisedBufferLength = 1 * 1024;
|
||||
static constexpr std::size_t kSampleBufferLength = 4 * 1024;
|
||||
|
||||
namespace audio {
|
||||
|
||||
@@ -34,20 +33,8 @@ SinkMixer::SinkMixer(StreamBufferHandle_t dest)
|
||||
resampler_(nullptr),
|
||||
source_(xStreamBufferCreate(kSourceBufferLength, 1)),
|
||||
sink_(dest) {
|
||||
input_stream_.reset(new RawStream(kInputBufferLength));
|
||||
floating_point_stream_.reset(new RawStream(kReformatBufferLength, MALLOC_CAP_SPIRAM));
|
||||
resampled_stream_.reset(new RawStream(kResampleBufferLength, MALLOC_CAP_SPIRAM));
|
||||
|
||||
quantisation_buffer_ = {
|
||||
reinterpret_cast<std::byte*>(heap_caps_malloc(
|
||||
kQuantisedBufferLength, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)),
|
||||
kQuantisedBufferLength};
|
||||
quantisation_buffer_as_ints_ = {
|
||||
reinterpret_cast<int*>(quantisation_buffer_.data()),
|
||||
quantisation_buffer_.size_bytes() / 4};
|
||||
quantisation_buffer_as_shorts_ = {
|
||||
reinterpret_cast<short*>(quantisation_buffer_.data()),
|
||||
quantisation_buffer_.size_bytes() / 2};
|
||||
input_stream_.reset(new RawStream(kSampleBufferLength));
|
||||
resampled_stream_.reset(new RawStream(kSampleBufferLength));
|
||||
|
||||
tasks::StartPersistent<tasks::Type::kMixer>([&]() { Main(); });
|
||||
}
|
||||
@@ -56,10 +43,6 @@ SinkMixer::~SinkMixer() {
|
||||
vQueueDelete(commands_);
|
||||
vSemaphoreDelete(is_idle_);
|
||||
vStreamBufferDelete(source_);
|
||||
heap_caps_free(quantisation_buffer_.data());
|
||||
if (resampler_ != nullptr) {
|
||||
src_delete(resampler_);
|
||||
}
|
||||
}
|
||||
|
||||
auto SinkMixer::MixAndSend(InputStream& input, const StreamInfo::Pcm& target)
|
||||
@@ -109,10 +92,12 @@ auto SinkMixer::Main() -> void {
|
||||
case Command::kSetSourceFormat:
|
||||
ESP_LOGI(kTag, "setting source format");
|
||||
input_receiver.prepare(args.format, {});
|
||||
resampler_.reset();
|
||||
break;
|
||||
case Command::kSetTargetFormat:
|
||||
ESP_LOGI(kTag, "setting target format");
|
||||
target_format_ = args.format;
|
||||
resampler_.reset();
|
||||
break;
|
||||
case Command::kReadBytes:
|
||||
xSemaphoreTake(is_idle_, 0);
|
||||
@@ -150,152 +135,84 @@ auto SinkMixer::HandleBytes() -> void {
|
||||
return;
|
||||
}
|
||||
|
||||
// Work out the resampling ratio using floating point arithmetic, since
|
||||
// relying on the FPU for this will be much faster, and the difference in
|
||||
// accuracy is unlikely to be noticeable.
|
||||
float src_ratio = static_cast<float>(target_format_.sample_rate) /
|
||||
static_cast<float>(pcm->sample_rate);
|
||||
|
||||
// Loop until we don't have any complete frames left in the input stream,
|
||||
// where a 'frame' is one complete sample per channel.
|
||||
while (!input_stream_->empty()) {
|
||||
// The first step of both resampling and requantising is to convert the
|
||||
// fixed point pcm input data into 32 bit floating point samples.
|
||||
OutputStream floating_writer{floating_point_stream_.get()};
|
||||
if (pcm->bits_per_sample == 16) {
|
||||
ConvertFixedToFloating<short>(input, floating_writer);
|
||||
RawStream* output_source;
|
||||
if (pcm->sample_rate != target_format_.sample_rate) {
|
||||
OutputStream resampled_writer{resampled_stream_.get()};
|
||||
if (Resample(input, resampled_writer)) {
|
||||
// Zero samples used or written. We need more input.
|
||||
break;
|
||||
}
|
||||
output_source = resampled_stream_.get();
|
||||
} else {
|
||||
// FIXME: We should consider treating 24 bit and 32 bit samples
|
||||
// differently.
|
||||
ConvertFixedToFloating<int>(input, floating_writer);
|
||||
output_source = input_stream_.get();
|
||||
}
|
||||
|
||||
InputStream floating_reader{floating_point_stream_.get()};
|
||||
if (target_format_.bits_per_sample == 16) {
|
||||
// This is slightly scary; we're basically reaching into the internals of
|
||||
// the stream buffer to do in-place conversion of samples. Saving an
|
||||
// extra buffer + copy into that buffer is certainly worth it however.
|
||||
cpp::span<sample::Sample> src =
|
||||
output_source->data_as<sample::Sample>().first(
|
||||
output_source->info().bytes_in_stream() / sizeof(sample::Sample));
|
||||
cpp::span<int16_t> dest = output_source->data_as<int16_t>().first(
|
||||
output_source->info().bytes_in_stream() / sizeof(int16_t));
|
||||
|
||||
while (!floating_point_stream_->empty()) {
|
||||
RawStream* quantisation_source;
|
||||
if (pcm->sample_rate != target_format_.sample_rate) {
|
||||
// The input data needs to be resampled before being sent to the sink.
|
||||
OutputStream resample_writer{resampled_stream_.get()};
|
||||
Resample(src_ratio, pcm->channels, floating_reader, resample_writer);
|
||||
quantisation_source = resampled_stream_.get();
|
||||
} else {
|
||||
// The input data already has an acceptable sample rate. All we need to
|
||||
// do is quantise it.
|
||||
quantisation_source = floating_point_stream_.get();
|
||||
}
|
||||
ApplyDither(src, 16);
|
||||
Downscale(src, dest);
|
||||
|
||||
InputStream quantise_reader{quantisation_source};
|
||||
while (!quantisation_source->empty()) {
|
||||
std::size_t samples_available;
|
||||
if (target_format_.bits_per_sample == 16) {
|
||||
samples_available = Quantise<short>(quantise_reader);
|
||||
} else {
|
||||
samples_available = Quantise<int>(quantise_reader);
|
||||
}
|
||||
|
||||
assert(samples_available * target_format_.real_bytes_per_sample() <=
|
||||
quantisation_buffer_.size_bytes());
|
||||
|
||||
std::size_t bytes_sent = xStreamBufferSend(
|
||||
sink_, quantisation_buffer_.data(),
|
||||
samples_available * target_format_.real_bytes_per_sample(),
|
||||
portMAX_DELAY);
|
||||
assert(bytes_sent ==
|
||||
samples_available * target_format_.real_bytes_per_sample());
|
||||
}
|
||||
output_source->info().bytes_in_stream() /= 2;
|
||||
}
|
||||
|
||||
InputStream output{output_source};
|
||||
cpp::span<const std::byte> buf = output.data();
|
||||
|
||||
size_t bytes_sent = 0;
|
||||
while (bytes_sent < buf.size_bytes()) {
|
||||
auto cropped = buf.subspan(bytes_sent);
|
||||
bytes_sent += xStreamBufferSend(sink_, cropped.data(),
|
||||
cropped.size_bytes(), portMAX_DELAY);
|
||||
}
|
||||
output.consume(bytes_sent);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
auto SinkMixer::ConvertFixedToFloating<short>(InputStream& in_str,
|
||||
OutputStream& out_str) -> void {
|
||||
auto in = in_str.data_as<short>();
|
||||
auto out = out_str.data_as<float>();
|
||||
std::size_t samples_converted = std::min(in.size(), out.size());
|
||||
|
||||
src_short_to_float_array(in.data(), out.data(), samples_converted);
|
||||
|
||||
in_str.consume(samples_converted * sizeof(short));
|
||||
out_str.add(samples_converted * sizeof(float));
|
||||
}
|
||||
|
||||
template <>
|
||||
auto SinkMixer::ConvertFixedToFloating<int>(InputStream& in_str,
|
||||
OutputStream& out_str) -> void {
|
||||
auto in = in_str.data_as<int>();
|
||||
auto out = out_str.data_as<float>();
|
||||
std::size_t samples_converted = std::min(in.size(), out.size());
|
||||
|
||||
src_int_to_float_array(in.data(), out.data(), samples_converted);
|
||||
|
||||
in_str.consume(samples_converted * sizeof(int));
|
||||
out_str.add(samples_converted * sizeof(float));
|
||||
}
|
||||
|
||||
auto SinkMixer::Resample(float src_ratio,
|
||||
int channels,
|
||||
InputStream& in,
|
||||
OutputStream& out) -> void {
|
||||
if (resampler_ == nullptr || src_get_channels(resampler_) != channels) {
|
||||
if (resampler_ != nullptr) {
|
||||
src_delete(resampler_);
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag, "creating new resampler with %u channels", channels);
|
||||
|
||||
int err = 0;
|
||||
resampler_ = src_new(SRC_LINEAR, channels, &err);
|
||||
assert(resampler_ != NULL);
|
||||
assert(err == 0);
|
||||
auto SinkMixer::Resample(InputStream& in, OutputStream& out) -> bool {
|
||||
if (resampler_ == nullptr) {
|
||||
ESP_LOGI(kTag, "creating new resampler");
|
||||
auto format = in.info().format_as<StreamInfo::Pcm>();
|
||||
resampler_.reset(new Resampler(
|
||||
format->sample_rate, target_format_.sample_rate, format->channels));
|
||||
}
|
||||
|
||||
auto in_buf = in.data_as<float>();
|
||||
auto out_buf = out.data_as<float>();
|
||||
auto res = resampler_->Process(in.data_as<sample::Sample>(),
|
||||
out.data_as<sample::Sample>(), false);
|
||||
|
||||
src_set_ratio(resampler_, src_ratio);
|
||||
SRC_DATA args{
|
||||
.data_in = in_buf.data(),
|
||||
.data_out = out_buf.data(),
|
||||
.input_frames = static_cast<long>(in_buf.size()),
|
||||
.output_frames = static_cast<long>(out_buf.size()),
|
||||
.input_frames_used = 0,
|
||||
.output_frames_gen = 0,
|
||||
.end_of_input = 0,
|
||||
.src_ratio = src_ratio,
|
||||
};
|
||||
int err = src_process(resampler_, &args);
|
||||
if (err != 0) {
|
||||
ESP_LOGE(kTag, "resampler error: %s", src_strerror(err));
|
||||
ESP_LOGI(kTag, "resampler sent %u samples, consumed %u, produced %u",
|
||||
in.data().size(), res.first, res.second);
|
||||
|
||||
in.consume(res.first * sizeof(sample::Sample));
|
||||
out.add(res.first * sizeof(sample::Sample));
|
||||
|
||||
return res.first == 0 && res.second == 0;
|
||||
}
|
||||
|
||||
auto SinkMixer::Downscale(cpp::span<sample::Sample> samples,
|
||||
cpp::span<int16_t> output) -> void {
|
||||
for (size_t i = 0; i < samples.size(); i++) {
|
||||
output[i] = sample::ToSigned16Bit(samples[i]);
|
||||
}
|
||||
|
||||
in.consume(args.input_frames_used * sizeof(float));
|
||||
out.add(args.output_frames_gen * sizeof(float));
|
||||
}
|
||||
|
||||
template <>
|
||||
auto SinkMixer::Quantise<short>(InputStream& in) -> std::size_t {
|
||||
auto src = in.data_as<float>();
|
||||
cpp::span<short> dest = quantisation_buffer_as_shorts_;
|
||||
dest = dest.first(std::min(src.size(), dest.size()));
|
||||
|
||||
src_float_to_short_array(src.data(), dest.data(), dest.size());
|
||||
|
||||
in.consume(dest.size() * sizeof(float));
|
||||
return dest.size();
|
||||
}
|
||||
|
||||
template <>
|
||||
auto SinkMixer::Quantise<int>(InputStream& in) -> std::size_t {
|
||||
auto src = in.data_as<float>();
|
||||
cpp::span<int> dest = quantisation_buffer_as_ints_;
|
||||
dest = dest.first(std::min<int>(src.size(), dest.size()));
|
||||
|
||||
src_float_to_int_array(src.data(), dest.data(), dest.size());
|
||||
|
||||
in.consume(dest.size() * sizeof(float));
|
||||
return dest.size();
|
||||
auto SinkMixer::ApplyDither(cpp::span<sample::Sample> samples,
|
||||
uint_fast8_t bits) -> void {
|
||||
static uint32_t prnd;
|
||||
for (auto& s : samples) {
|
||||
prnd = (prnd * 0x19660dL + 0x3c6ef35fL) & 0xffffffffL;
|
||||
s = sample::Clip(
|
||||
static_cast<int64_t>(s) +
|
||||
(static_cast<int>(prnd) >> (sizeof(sample::Sample) - bits)));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "foxen/flac.h"
|
||||
#include "sample.hpp"
|
||||
|
||||
namespace codecs {
|
||||
|
||||
@@ -47,7 +48,6 @@ auto FoxenFlacDecoder::BeginStream(const cpp::span<const std::byte> input)
|
||||
|
||||
OutputFormat format{
|
||||
.num_channels = static_cast<uint8_t>(channels),
|
||||
.bits_per_sample = 32, // libfoxenflac output is fixed-size.
|
||||
.sample_rate_hz = static_cast<uint32_t>(fs),
|
||||
.duration_seconds = {},
|
||||
.bits_per_second = {},
|
||||
@@ -62,7 +62,7 @@ auto FoxenFlacDecoder::BeginStream(const cpp::span<const std::byte> input)
|
||||
}
|
||||
|
||||
auto FoxenFlacDecoder::ContinueStream(cpp::span<const std::byte> input,
|
||||
cpp::span<std::byte> output)
|
||||
cpp::span<sample::Sample> output)
|
||||
-> Result<OutputInfo> {
|
||||
cpp::span<int32_t> output_as_samples{
|
||||
reinterpret_cast<int32_t*>(output.data()), output.size_bytes() / 4};
|
||||
@@ -78,7 +78,7 @@ auto FoxenFlacDecoder::ContinueStream(cpp::span<const std::byte> input,
|
||||
|
||||
if (samples_written > 0) {
|
||||
return {bytes_read,
|
||||
OutputInfo{.bytes_written = samples_written * 4,
|
||||
OutputInfo{.samples_written = samples_written,
|
||||
.is_finished_writing = state == FLAC_END_OF_FRAME}};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "sample.hpp"
|
||||
#include "result.hpp"
|
||||
#include "span.hpp"
|
||||
#include "types.hpp"
|
||||
@@ -61,7 +62,6 @@ class ICodec {
|
||||
|
||||
struct OutputFormat {
|
||||
uint8_t num_channels;
|
||||
uint8_t bits_per_sample;
|
||||
uint32_t sample_rate_hz;
|
||||
|
||||
std::optional<uint32_t> duration_seconds;
|
||||
@@ -76,7 +76,7 @@ class ICodec {
|
||||
-> Result<OutputFormat> = 0;
|
||||
|
||||
struct OutputInfo {
|
||||
std::size_t bytes_written;
|
||||
std::size_t samples_written;
|
||||
bool is_finished_writing;
|
||||
};
|
||||
|
||||
@@ -84,7 +84,7 @@ class ICodec {
|
||||
* Writes PCM samples to the given output buffer.
|
||||
*/
|
||||
virtual auto ContinueStream(cpp::span<const std::byte> input,
|
||||
cpp::span<std::byte> output)
|
||||
cpp::span<sample::Sample> output)
|
||||
-> Result<OutputInfo> = 0;
|
||||
|
||||
virtual auto SeekStream(cpp::span<const std::byte> input,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "foxen/flac.h"
|
||||
#include "sample.hpp"
|
||||
#include "span.hpp"
|
||||
|
||||
#include "codec.hpp"
|
||||
@@ -26,7 +27,7 @@ class FoxenFlacDecoder : public ICodec {
|
||||
~FoxenFlacDecoder();
|
||||
|
||||
auto BeginStream(cpp::span<const std::byte>) -> Result<OutputFormat> override;
|
||||
auto ContinueStream(cpp::span<const std::byte>, cpp::span<std::byte>)
|
||||
auto ContinueStream(cpp::span<const std::byte>, cpp::span<sample::Sample>)
|
||||
-> Result<OutputInfo> override;
|
||||
auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample)
|
||||
-> Result<void> override;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "mad.h"
|
||||
#include "sample.hpp"
|
||||
#include "span.hpp"
|
||||
|
||||
#include "codec.hpp"
|
||||
@@ -35,7 +36,7 @@ class MadMp3Decoder : public ICodec {
|
||||
* Writes samples for the current frame.
|
||||
*/
|
||||
auto ContinueStream(cpp::span<const std::byte> input,
|
||||
cpp::span<std::byte> output)
|
||||
cpp::span<sample::Sample> output)
|
||||
-> Result<OutputInfo> override;
|
||||
|
||||
auto SeekStream(cpp::span<const std::byte> input, std::size_t target_sample)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <mad.h>
|
||||
|
||||
namespace sample {
|
||||
|
||||
// A signed, 32-bit PCM sample.
|
||||
typedef int32_t Sample;
|
||||
|
||||
constexpr auto Clip(int64_t v) -> Sample {
|
||||
if (v > INT32_MAX)
|
||||
return INT32_MAX;
|
||||
if (v < INT32_MIN)
|
||||
return INT32_MIN;
|
||||
return v;
|
||||
}
|
||||
|
||||
constexpr auto FromSigned(int32_t src, uint_fast8_t bits) -> Sample {
|
||||
// Left-align samples, effectively scaling them up to 32 bits.
|
||||
return src << (sizeof(Sample) * 8 - bits);
|
||||
}
|
||||
|
||||
constexpr auto FromUnsigned(uint32_t src, uint_fast8_t bits) -> Sample {
|
||||
// Left-align, then substract the max value / 2 to make the sample centred
|
||||
// around zero.
|
||||
return (src << (sizeof(uint32_t) * 8 - bits)) - (~0UL >> 1);
|
||||
}
|
||||
|
||||
constexpr auto FromFloat(float src) -> Sample {
|
||||
return std::clamp<float>(src, -1.0f, 1.0f) * static_cast<float>(INT32_MAX);
|
||||
}
|
||||
|
||||
constexpr auto FromDouble(double src) -> Sample {
|
||||
return std::clamp<double>(src, -1.0, 1.0) * static_cast<double>(INT32_MAX);
|
||||
}
|
||||
|
||||
constexpr auto FromMad(mad_fixed_t src) -> Sample {
|
||||
// Round the bottom bits.
|
||||
src += (1L << (MAD_F_FRACBITS - 24));
|
||||
|
||||
// Clip the leftover bits to within range.
|
||||
if (src >= MAD_F_ONE)
|
||||
src = MAD_F_ONE - 1;
|
||||
else if (src < -MAD_F_ONE)
|
||||
src = -MAD_F_ONE;
|
||||
|
||||
// Quantize.
|
||||
return FromSigned(src >> (MAD_F_FRACBITS + 1 - 24), 24);
|
||||
}
|
||||
|
||||
constexpr auto ToSigned16Bit(Sample src) -> uint16_t {
|
||||
return src >> 16;
|
||||
}
|
||||
|
||||
} // namespace sample
|
||||
+9
-31
@@ -17,24 +17,11 @@
|
||||
#include "codec.hpp"
|
||||
#include "esp_log.h"
|
||||
#include "result.hpp"
|
||||
#include "sample.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
namespace codecs {
|
||||
|
||||
static uint32_t mad_fixed_to_pcm(mad_fixed_t sample, uint8_t bits) {
|
||||
// Round the bottom bits.
|
||||
sample += (1L << (MAD_F_FRACBITS - bits));
|
||||
|
||||
// Clip the leftover bits to within range.
|
||||
if (sample >= MAD_F_ONE)
|
||||
sample = MAD_F_ONE - 1;
|
||||
else if (sample < -MAD_F_ONE)
|
||||
sample = -MAD_F_ONE;
|
||||
|
||||
// Quantize.
|
||||
return sample >> (MAD_F_FRACBITS + 1 - bits);
|
||||
}
|
||||
|
||||
MadMp3Decoder::MadMp3Decoder() {
|
||||
mad_stream_init(&stream_);
|
||||
mad_frame_init(&frame_);
|
||||
@@ -83,7 +70,6 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
|
||||
uint8_t channels = MAD_NCHANNELS(&header);
|
||||
OutputFormat output{
|
||||
.num_channels = channels,
|
||||
.bits_per_sample = 24, // We always scale to 24 bits
|
||||
.sample_rate_hz = header.samplerate,
|
||||
.duration_seconds = {},
|
||||
.bits_per_second = {},
|
||||
@@ -100,7 +86,7 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
|
||||
}
|
||||
|
||||
auto MadMp3Decoder::ContinueStream(cpp::span<const std::byte> input,
|
||||
cpp::span<std::byte> output)
|
||||
cpp::span<sample::Sample> output)
|
||||
-> Result<OutputInfo> {
|
||||
std::size_t bytes_read = 0;
|
||||
if (current_sample_ < 0) {
|
||||
@@ -133,32 +119,24 @@ auto MadMp3Decoder::ContinueStream(cpp::span<const std::byte> input,
|
||||
bytes_read = GetBytesUsed(input.size_bytes());
|
||||
}
|
||||
|
||||
size_t output_byte = 0;
|
||||
size_t output_sample = 0;
|
||||
while (current_sample_ < synth_.pcm.length) {
|
||||
if (output_byte + (4 * synth_.pcm.channels) >= output.size()) {
|
||||
// We can't fit the next sample into the buffer. Stop now, and also avoid
|
||||
// writing the sample for only half the channels.
|
||||
return {bytes_read, OutputInfo{.bytes_written = output_byte,
|
||||
if (output_sample + synth_.pcm.channels >= output.size()) {
|
||||
// We can't fit the next full frame into the buffer.
|
||||
return {bytes_read, OutputInfo{.samples_written = output_sample,
|
||||
.is_finished_writing = false}};
|
||||
}
|
||||
|
||||
for (int channel = 0; channel < synth_.pcm.channels; channel++) {
|
||||
uint32_t sample_24 =
|
||||
mad_fixed_to_pcm(synth_.pcm.samples[channel][current_sample_], 24);
|
||||
|
||||
// 24 bit samples must still be aligned to 32 bits. The LSB is ignored.
|
||||
output[output_byte++] = static_cast<std::byte>(0);
|
||||
|
||||
output[output_byte++] = static_cast<std::byte>((sample_24)&0xFF);
|
||||
output[output_byte++] = static_cast<std::byte>((sample_24 >> 8) & 0xFF);
|
||||
output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF);
|
||||
output[output_sample++] =
|
||||
sample::FromMad(synth_.pcm.samples[channel][current_sample_]);
|
||||
}
|
||||
current_sample_++;
|
||||
}
|
||||
|
||||
// We wrote everything! Reset, ready for the next frame.
|
||||
current_sample_ = -1;
|
||||
return {bytes_read, OutputInfo{.bytes_written = output_byte,
|
||||
return {bytes_read, OutputInfo{.samples_written = output_sample,
|
||||
.is_finished_writing = true}};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
#include "sample.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
namespace sample {
|
||||
|
||||
void siconv(int* dst, uint8_t* src, int bits, int skip, int count) {
|
||||
int i, v, s, b;
|
||||
|
||||
b = (bits + 7) / 8;
|
||||
s = sizeof(int) * 8 - bits;
|
||||
while (count--) {
|
||||
v = 0;
|
||||
i = b;
|
||||
switch (b) {
|
||||
case 4:
|
||||
v = src[--i];
|
||||
case 3:
|
||||
v = (v << 8) | src[--i];
|
||||
case 2:
|
||||
v = (v << 8) | src[--i];
|
||||
case 1:
|
||||
v = (v << 8) | src[--i];
|
||||
}
|
||||
*dst++ = v << s;
|
||||
src += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Siconv(int* dst, uint8_t* src, int bits, int skip, int count) {
|
||||
int i, v, s, b;
|
||||
|
||||
b = (bits + 7) / 8;
|
||||
s = sizeof(int) * 8 - bits;
|
||||
while (count--) {
|
||||
v = 0;
|
||||
i = 0;
|
||||
switch (b) {
|
||||
case 4:
|
||||
v = src[i++];
|
||||
case 3:
|
||||
v = (v << 8) | src[i++];
|
||||
case 2:
|
||||
v = (v << 8) | src[i++];
|
||||
case 1:
|
||||
v = (v << 8) | src[i];
|
||||
}
|
||||
*dst++ = v << s;
|
||||
src += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void uiconv(int* dst, uint8_t* src, int bits, int skip, int count) {
|
||||
int i, s, b;
|
||||
uint32_t v;
|
||||
|
||||
b = (bits + 7) / 8;
|
||||
s = sizeof(uint32_t) * 8 - bits;
|
||||
while (count--) {
|
||||
v = 0;
|
||||
i = b;
|
||||
switch (b) {
|
||||
case 4:
|
||||
v = src[--i];
|
||||
case 3:
|
||||
v = (v << 8) | src[--i];
|
||||
case 2:
|
||||
v = (v << 8) | src[--i];
|
||||
case 1:
|
||||
v = (v << 8) | src[--i];
|
||||
}
|
||||
*dst++ = (v << s) - (~0UL >> 1);
|
||||
src += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Uiconv(int* dst, uint8_t* src, int bits, int skip, int count) {
|
||||
int i, s, b;
|
||||
uint32_t v;
|
||||
|
||||
b = (bits + 7) / 8;
|
||||
s = sizeof(uint32_t) * 8 - bits;
|
||||
while (count--) {
|
||||
v = 0;
|
||||
i = 0;
|
||||
switch (b) {
|
||||
case 4:
|
||||
v = src[i++];
|
||||
case 3:
|
||||
v = (v << 8) | src[i++];
|
||||
case 2:
|
||||
v = (v << 8) | src[i++];
|
||||
case 1:
|
||||
v = (v << 8) | src[i];
|
||||
}
|
||||
*dst++ = (v << s) - (~0UL >> 1);
|
||||
src += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void ficonv(int* dst, uint8_t* src, int bits, int skip, int count) {
|
||||
if (bits == 32) {
|
||||
while (count--) {
|
||||
float f;
|
||||
|
||||
f = *((float*)src), src += skip;
|
||||
if (f > 1.0)
|
||||
*dst++ = INT32_MAX;
|
||||
else if (f < -1.0)
|
||||
*dst++ = INT32_MIN;
|
||||
else
|
||||
*dst++ = f * ((float)INT32_MAX);
|
||||
}
|
||||
} else {
|
||||
while (count--) {
|
||||
double d;
|
||||
|
||||
d = *((double*)src), src += skip;
|
||||
if (d > 1.0)
|
||||
*dst++ = INT32_MAX;
|
||||
else if (d < -1.0)
|
||||
*dst++ = INT32_MIN;
|
||||
else
|
||||
*dst++ = d * ((double)INT32_MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void aiconv(int* dst, uint8_t* src, int, int skip, int count) {
|
||||
int t, seg;
|
||||
uint8_t a;
|
||||
|
||||
while (count--) {
|
||||
a = *src, src += skip;
|
||||
a ^= 0x55;
|
||||
t = (a & 0xf) << 4;
|
||||
seg = (a & 0x70) >> 4;
|
||||
switch (seg) {
|
||||
case 0:
|
||||
t += 8;
|
||||
break;
|
||||
case 1:
|
||||
t += 0x108;
|
||||
break;
|
||||
default:
|
||||
t += 0x108;
|
||||
t <<= seg - 1;
|
||||
}
|
||||
t = (a & 0x80) ? t : -t;
|
||||
*dst++ = t << (sizeof(int) * 8 - 16);
|
||||
}
|
||||
}
|
||||
|
||||
void µiconv(int* dst, uint8_t* src, int, int skip, int count) {
|
||||
int t;
|
||||
uint8_t u;
|
||||
|
||||
while (count--) {
|
||||
u = *src, src += skip;
|
||||
u = ~u;
|
||||
t = ((u & 0xf) << 3) + 0x84;
|
||||
t <<= (u & 0x70) >> 4;
|
||||
t = u & 0x80 ? 0x84 - t : t - 0x84;
|
||||
*dst++ = t << (sizeof(int) * 8 - 16);
|
||||
}
|
||||
}
|
||||
|
||||
void soconv(int* src, uint8_t* dst, int bits, int skip, int count) {
|
||||
int i, v, s, b;
|
||||
|
||||
b = (bits + 7) / 8;
|
||||
s = sizeof(int) * 8 - bits;
|
||||
while (count--) {
|
||||
v = *src++ >> s;
|
||||
i = 0;
|
||||
switch (b) {
|
||||
case 4:
|
||||
dst[i++] = v, v >>= 8;
|
||||
case 3:
|
||||
dst[i++] = v, v >>= 8;
|
||||
case 2:
|
||||
dst[i++] = v, v >>= 8;
|
||||
case 1:
|
||||
dst[i] = v;
|
||||
}
|
||||
dst += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Soconv(int* src, uint8_t* dst, int bits, int skip, int count) {
|
||||
int i, v, s, b;
|
||||
|
||||
b = (bits + 7) / 8;
|
||||
s = sizeof(int) * 8 - bits;
|
||||
while (count--) {
|
||||
v = *src++ >> s;
|
||||
i = b;
|
||||
switch (b) {
|
||||
case 4:
|
||||
dst[--i] = v, v >>= 8;
|
||||
case 3:
|
||||
dst[--i] = v, v >>= 8;
|
||||
case 2:
|
||||
dst[--i] = v, v >>= 8;
|
||||
case 1:
|
||||
dst[--i] = v;
|
||||
}
|
||||
dst += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void uoconv(int* src, uint8_t* dst, int bits, int skip, int count) {
|
||||
int i, s, b;
|
||||
uint32_t v;
|
||||
|
||||
b = (bits + 7) / 8;
|
||||
s = sizeof(uint32_t) * 8 - bits;
|
||||
while (count--) {
|
||||
v = ((~0UL >> 1) + *src++) >> s;
|
||||
i = 0;
|
||||
switch (b) {
|
||||
case 4:
|
||||
dst[i++] = v, v >>= 8;
|
||||
case 3:
|
||||
dst[i++] = v, v >>= 8;
|
||||
case 2:
|
||||
dst[i++] = v, v >>= 8;
|
||||
case 1:
|
||||
dst[i] = v;
|
||||
}
|
||||
dst += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void Uoconv(int* src, uint8_t* dst, int bits, int skip, int count) {
|
||||
int i, s, b;
|
||||
uint32_t v;
|
||||
|
||||
b = (bits + 7) / 8;
|
||||
s = sizeof(uint32_t) * 8 - bits;
|
||||
while (count--) {
|
||||
v = ((~0UL >> 1) + *src++) >> s;
|
||||
i = b;
|
||||
switch (b) {
|
||||
case 4:
|
||||
dst[--i] = v, v >>= 8;
|
||||
case 3:
|
||||
dst[--i] = v, v >>= 8;
|
||||
case 2:
|
||||
dst[--i] = v, v >>= 8;
|
||||
case 1:
|
||||
dst[--i] = v;
|
||||
}
|
||||
dst += skip;
|
||||
}
|
||||
}
|
||||
|
||||
void foconv(int* src, uint8_t* dst, int bits, int skip, int count) {
|
||||
if (bits == 32) {
|
||||
while (count--) {
|
||||
*((float*)dst) = *src++ / ((float)INT32_MAX);
|
||||
dst += skip;
|
||||
}
|
||||
} else {
|
||||
while (count--) {
|
||||
*((double*)dst) = *src++ / ((double)INT32_MAX);
|
||||
dst += skip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user